let array = []; let arraySize = 20; let selectedAlgorithm = 'bubble'; let sorting = false; let comparisons = 0; let swaps = 0; let attempts = 0; const asciiChars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; // Audio context and sound system let audioContext; let isSoundEnabled = true; let slideWhistleOscillator; let slideWhistleGain; let startTime; let totalSortingTime = 10; // Will be dynamically adjusted function initAudio() { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } } function startSlideWhistle() { if (!isSoundEnabled || !audioContext) return; // Create oscillator and gain for slide whistle slideWhistleOscillator = audioContext.createOscillator(); slideWhistleGain = audioContext.createGain(); slideWhistleOscillator.type = 'sine'; // Start at low frequency slideWhistleOscillator.frequency.setValueAtTime(200, audioContext.currentTime); // Estimate sorting time based on array size and algorithm totalSortingTime = estimateSortingTime(); // Schedule a smooth frequency ramp over the estimated time slideWhistleOscillator.frequency.exponentialRampToValueAtTime( 800, audioContext.currentTime + totalSortingTime ); slideWhistleGain.gain.setValueAtTime(0.15, audioContext.currentTime); slideWhistleOscillator.connect(slideWhistleGain); slideWhistleGain.connect(audioContext.destination); slideWhistleOscillator.start(); startTime = audioContext.currentTime; } function estimateSortingTime() { // Estimate based on array size and algorithm complexity const baseTime = array.length * getDelay() / 1000; switch(selectedAlgorithm) { case 'bubble': case 'selection': case 'insertion': case 'cocktail': return baseTime * array.length * 0.5; case 'quick': case 'merge': case 'heap': return baseTime * Math.log2(array.length) * 2; case 'shell': return baseTime * array.length * 0.3; case 'radix': return baseTime * 3; case 'gnome': return baseTime * array.length * 0.7; case 'bogo': return baseTime * 10; // Who knows! default: return baseTime * array.length * 0.5; } } function updateSlideWhistle(progress) { // With exponential ramping, we don't need to update manually // The Web Audio API handles the smooth transition } function stopSlideWhistle() { if (!slideWhistleOscillator) return; // Cancel any scheduled frequency changes slideWhistleOscillator.frequency.cancelScheduledValues(audioContext.currentTime); // Set current frequency and fade out const currentFreq = slideWhistleOscillator.frequency.value; slideWhistleOscillator.frequency.setValueAtTime(currentFreq, audioContext.currentTime); // Smooth fade out slideWhistleGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.2); setTimeout(() => { if (slideWhistleOscillator) { slideWhistleOscillator.stop(); slideWhistleOscillator = null; slideWhistleGain = null; } }, 200); } function playZoot() { if (!isSoundEnabled || !audioContext) return; // Create a fun "zoot" sound with multiple components const oscillator1 = audioContext.createOscillator(); const oscillator2 = audioContext.createOscillator(); const oscillator3 = audioContext.createOscillator(); const gainNode = audioContext.createGain(); const filter = audioContext.createBiquadFilter(); // Three oscillators for a richer, more satisfying sound oscillator1.type = 'sawtooth'; oscillator2.type = 'square'; oscillator3.type = 'triangle'; // Quick frequency sweep for "zoot" effect const now = audioContext.currentTime; // Main sweep oscillator1.frequency.setValueAtTime(800, now); oscillator1.frequency.exponentialRampToValueAtTime(1600, now + 0.05); oscillator1.frequency.exponentialRampToValueAtTime(400, now + 0.15); oscillator1.frequency.exponentialRampToValueAtTime(800, now + 0.25); // Harmonic sweep oscillator2.frequency.setValueAtTime(400, now); oscillator2.frequency.exponentialRampToValueAtTime(800, now + 0.05); oscillator2.frequency.exponentialRampToValueAtTime(200, now + 0.15); oscillator2.frequency.exponentialRampToValueAtTime(400, now + 0.25); // Sub bass sweep oscillator3.frequency.setValueAtTime(100, now); oscillator3.frequency.exponentialRampToValueAtTime(200, now + 0.1); oscillator3.frequency.exponentialRampToValueAtTime(50, now + 0.25); // Filter sweep for extra "zoot" character filter.type = 'bandpass'; filter.frequency.setValueAtTime(400, now); filter.frequency.exponentialRampToValueAtTime(2000, now + 0.1); filter.frequency.exponentialRampToValueAtTime(800, now + 0.25); filter.Q.value = 5; // Envelope gainNode.gain.setValueAtTime(0, now); gainNode.gain.linearRampToValueAtTime(0.4, now + 0.02); gainNode.gain.linearRampToValueAtTime(0.3, now + 0.1); gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.4); // Connect oscillator1.connect(filter); oscillator2.connect(filter); oscillator3.connect(filter); filter.connect(gainNode); gainNode.connect(audioContext.destination); // Play oscillator1.start(); oscillator2.start(); oscillator3.start(); oscillator1.stop(now + 0.4); oscillator2.stop(now + 0.4); oscillator3.stop(now + 0.4); } // Add a function to calculate sorting progress function getSortingProgress() { let sortedCount = 0; const rows = document.querySelectorAll('.bar-row'); rows.forEach(row => { if (row.classList.contains('sorted')) { sortedCount++; } }); return sortedCount / array.length; } // Toggle sound on/off function toggleSound() { isSoundEnabled = !isSoundEnabled; const soundBtn = document.getElementById('soundBtn'); soundBtn.textContent = isSoundEnabled ? 'SOUND: ON' : 'SOUND: OFF'; // If sound is turned off while playing, stop the whistle if (!isSoundEnabled && slideWhistleOscillator) { stopSlideWhistle(); } } function generateArray() { // Use smaller array for Bogo Sort if (selectedAlgorithm === 'bogo') { arraySize = 5; // Much smaller for Bogo! document.getElementById('arraySize').textContent = arraySize; } else if (arraySize === 5 && selectedAlgorithm !== 'bogo') { arraySize = 20; // Reset to normal size document.getElementById('arraySize').textContent = arraySize; } array = []; for (let i = 0; i < arraySize; i++) { array.push(Math.floor(Math.random() * 90) + 10); } renderArray(); resetStats(); } function getAsciiBar(value, maxValue) { const normalized = value / maxValue; const maxBarLength = window.innerWidth < 768 ? 20 : 40; const barLength = Math.floor(normalized * maxBarLength); let bar = ''; for (let i = 0; i < barLength; i++) { bar += asciiChars[Math.min(7, Math.floor(i / 5))]; } return bar; } function renderArray() { const container = document.getElementById('visualization'); const maxValue = Math.max(...array); const isMobile = window.innerWidth < 768; const borderLength = isMobile ? 30 : 50; const padLength = isMobile ? 20 : 40; let asciiDisplay = ''; // Top border asciiDisplay += '┌' + '─'.repeat(borderLength) + '┐\n'; // Array visualization array.forEach((value, index) => { const bar = getAsciiBar(value, maxValue); const paddedValue = String(value).padStart(3, '0'); const paddedIndex = String(index).padStart(2, '0'); if (isMobile) { asciiDisplay += `│${paddedIndex}:[${paddedValue}]${bar.padEnd(padLength, ' ')}│\n`; } else { asciiDisplay += `│ ${paddedIndex}: [${paddedValue}] ${bar.padEnd(padLength, ' ')}│\n`; } }); // Bottom border asciiDisplay += '└' + '─'.repeat(borderLength) + '┘'; container.innerHTML = asciiDisplay.split('\n').map((line, index) => { if (index > 0 && index <= array.length) { return `${line}`; } return `${line}`; }).join('\n'); } function resetStats() { comparisons = 0; swaps = 0; attempts = 0; updateStats(); } function updateStats() { document.getElementById('comparisons').textContent = String(comparisons).padStart(4, '0'); document.getElementById('swaps').textContent = String(swaps).padStart(4, '0'); document.getElementById('attempts').textContent = String(attempts).padStart(4, '0'); } function selectAlgorithm(algo) { selectedAlgorithm = algo; document.querySelectorAll('.algorithm-btn').forEach(btn => { btn.classList.remove('active'); }); event.target.classList.add('active'); // Show/hide attempts counter for Bogo Sort const attemptsContainer = document.getElementById('attemptsContainer'); if (algo === 'bogo') { attemptsContainer.style.display = 'block'; generateArray(); // Auto-generate smaller array for Bogo } else { attemptsContainer.style.display = 'none'; if (arraySize === 5) { generateArray(); // Reset to normal size } } } async function startSort() { if (sorting) return; sorting = true; document.getElementById('startBtn').disabled = true; resetStats(); // Initialize audio on user interaction initAudio(); // Start slide whistle startSlideWhistle(); switch (selectedAlgorithm) { case 'bubble': await bubbleSort(); break; case 'selection': await selectionSort(); break; case 'insertion': await insertionSort(); break; case 'quick': await quickSort(0, array.length - 1); break; case 'merge': await mergeSort(0, array.length - 1); break; case 'heap': await heapSort(); break; case 'shell': await shellSort(); break; case 'cocktail': await cocktailSort(); break; case 'radix': await radixSort(); break; case 'gnome': await gnomeSort(); break; case 'bogo': await bogoSort(); break; } // Stop slide whistle and play zoot stopSlideWhistle(); playZoot(); markAllSorted(); sorting = false; document.getElementById('startBtn').disabled = false; } function getDelay() { return 101 - document.getElementById('speedSlider').value; } async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function compare(i, j) { comparisons++; updateStats(); const rows = document.querySelectorAll('.bar-row'); if (rows[i]) rows[i].classList.add('comparing'); if (rows[j]) rows[j].classList.add('comparing'); // Subtle pitch update during comparisons for continuous glissando const progress = getSortingProgress(); updateSlideWhistle(progress); await sleep(getDelay()); if (rows[i]) rows[i].classList.remove('comparing'); if (rows[j]) rows[j].classList.remove('comparing'); } async function swap(i, j) { swaps++; updateStats(); const temp = array[i]; array[i] = array[j]; array[j] = temp; renderArray(); // Update slide whistle pitch based on progress const progress = getSortingProgress(); updateSlideWhistle(progress); await sleep(getDelay()); } async function bubbleSort() { for (let i = 0; i < array.length - 1; i++) { for (let j = 0; j < array.length - i - 1; j++) { await compare(j, j + 1); if (array[j] > array[j + 1]) { await swap(j, j + 1); } } document.querySelectorAll('.bar-row')[array.length - i - 1].classList.add('sorted'); // Update slide whistle pitch const progress = getSortingProgress(); updateSlideWhistle(progress); } document.querySelectorAll('.bar-row')[0].classList.add('sorted'); } async function selectionSort() { for (let i = 0; i < array.length - 1; i++) { let minIdx = i; for (let j = i + 1; j < array.length; j++) { await compare(minIdx, j); if (array[j] < array[minIdx]) { minIdx = j; } } if (minIdx !== i) { await swap(i, minIdx); } document.querySelectorAll('.bar-row')[i].classList.add('sorted'); // Update slide whistle pitch const progress = getSortingProgress(); updateSlideWhistle(progress); } document.querySelectorAll('.bar-row')[array.length - 1].classList.add('sorted'); } async function insertionSort() { for (let i = 1; i < array.length; i++) { let j = i - 1; let key = array[i]; while (j >= 0 && array[j] > key) { await compare(j, j + 1); array[j + 1] = array[j]; renderArray(); j--; await sleep(getDelay()); } array[j + 1] = key; renderArray(); document.querySelectorAll('.bar-row')[j + 1].classList.add('sorted'); } markAllSorted(); } async function quickSort(low, high) { if (low < high) { let pi = await partition(low, high); await quickSort(low, pi - 1); await quickSort(pi + 1, high); } } async function partition(low, high) { let pivot = array[high]; let i = low - 1; for (let j = low; j < high; j++) { await compare(j, high); if (array[j] < pivot) { i++; await swap(i, j); } } await swap(i + 1, high); return i + 1; } async function mergeSort(left, right) { if (left < right) { let mid = Math.floor((left + right) / 2); await mergeSort(left, mid); await mergeSort(mid + 1, right); await merge(left, mid, right); } } async function merge(left, mid, right) { let leftArr = array.slice(left, mid + 1); let rightArr = array.slice(mid + 1, right + 1); let i = 0, j = 0, k = left; while (i < leftArr.length && j < rightArr.length) { await compare(left + i, mid + 1 + j); if (leftArr[i] <= rightArr[j]) { array[k] = leftArr[i]; i++; } else { array[k] = rightArr[j]; j++; } k++; renderArray(); await sleep(getDelay()); } while (i < leftArr.length) { array[k] = leftArr[i]; i++; k++; renderArray(); await sleep(getDelay()); } while (j < rightArr.length) { array[k] = rightArr[j]; j++; k++; renderArray(); await sleep(getDelay()); } } // Heap Sort async function heapSort() { // Build max heap for (let i = Math.floor(array.length / 2) - 1; i >= 0; i--) { await heapify(array.length, i); } // Extract elements from heap one by one for (let i = array.length - 1; i > 0; i--) { await swap(0, i); document.querySelectorAll('.bar-row')[i].classList.add('sorted'); await heapify(i, 0); // Update slide whistle pitch const progress = getSortingProgress(); updateSlideWhistle(progress); } document.querySelectorAll('.bar-row')[0].classList.add('sorted'); } async function heapify(n, i) { let largest = i; let left = 2 * i + 1; let right = 2 * i + 2; if (left < n) { await compare(left, largest); if (array[left] > array[largest]) { largest = left; } } if (right < n) { await compare(right, largest); if (array[right] > array[largest]) { largest = right; } } if (largest !== i) { await swap(i, largest); await heapify(n, largest); } } // Shell Sort async function shellSort() { let gap = Math.floor(array.length / 2); while (gap > 0) { for (let i = gap; i < array.length; i++) { let temp = array[i]; let j = i; while (j >= gap) { await compare(j - gap, j); if (array[j - gap] > temp) { array[j] = array[j - gap]; j -= gap; renderArray(); await sleep(getDelay()); } else { break; } } array[j] = temp; renderArray(); await sleep(getDelay()); } gap = Math.floor(gap / 2); } // Mark all as sorted for (let i = 0; i < array.length; i++) { document.querySelectorAll('.bar-row')[i].classList.add('sorted'); } } // Cocktail Sort (Bidirectional Bubble Sort) async function cocktailSort() { let swapped = true; let start = 0; let end = array.length - 1; while (swapped) { swapped = false; // Forward pass (like bubble sort) for (let i = start; i < end; i++) { await compare(i, i + 1); if (array[i] > array[i + 1]) { await swap(i, i + 1); swapped = true; } } if (!swapped) break; document.querySelectorAll('.bar-row')[end].classList.add('sorted'); end--; swapped = false; // Backward pass for (let i = end; i > start; i--) { await compare(i, i - 1); if (array[i] < array[i - 1]) { await swap(i, i - 1); swapped = true; } } document.querySelectorAll('.bar-row')[start].classList.add('sorted'); start++; } // Mark remaining elements as sorted for (let i = start; i <= end; i++) { document.querySelectorAll('.bar-row')[i].classList.add('sorted'); } } // Radix Sort (LSD - Least Significant Digit) async function radixSort() { // Find the maximum number to know number of digits let max = Math.max(...array); // Do counting sort for every digit for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) { await countingSortByDigit(exp); } // Mark all as sorted for (let i = 0; i < array.length; i++) { document.querySelectorAll('.bar-row')[i].classList.add('sorted'); } } async function countingSortByDigit(exp) { let output = new Array(array.length); let count = new Array(10).fill(0); // Store count of occurrences for (let i = 0; i < array.length; i++) { let digit = Math.floor(array[i] / exp) % 10; count[digit]++; } // Change count[i] so it contains actual position for (let i = 1; i < 10; i++) { count[i] += count[i - 1]; } // Build the output array for (let i = array.length - 1; i >= 0; i--) { let digit = Math.floor(array[i] / exp) % 10; output[count[digit] - 1] = array[i]; count[digit]--; // Visual comparison for the digit if (i < array.length - 1) { await compare(i, i + 1); } } // Copy the output array to arr[] for (let i = 0; i < array.length; i++) { array[i] = output[i]; renderArray(); await sleep(getDelay()); } } // Gnome Sort (also called Stupid Sort) async function gnomeSort() { let index = 0; while (index < array.length) { if (index == 0) { index++; } else { await compare(index, index - 1); if (array[index] >= array[index - 1]) { document.querySelectorAll('.bar-row')[index - 1].classList.add('sorted'); index++; } else { await swap(index, index - 1); index--; } } } // Mark the last element as sorted if (array.length > 0) { document.querySelectorAll('.bar-row')[array.length - 1].classList.add('sorted'); } } // Bogo Sort (Warning: This is intentionally terrible!) async function bogoSort() { attempts = 0; const maxAttempts = 10000; // Safety limit to prevent infinite loops while (!isSorted() && attempts < maxAttempts) { // Shuffle the array randomly await shuffleArray(); attempts++; updateStats(); } if (attempts >= maxAttempts) { alert("Bogo Sort gave up after " + maxAttempts + " attempts! Try a smaller array."); } // Mark all as sorted for (let i = 0; i < array.length; i++) { document.querySelectorAll('.bar-row')[i].classList.add('sorted'); } } function isSorted() { for (let i = 0; i < array.length - 1; i++) { if (array[i] > array[i + 1]) { return false; } } return true; } async function shuffleArray() { // Fisher-Yates shuffle with visualization for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); await compare(i, j); await swap(i, j); } } function markAllSorted() { document.querySelectorAll('.bar-row').forEach(row => { row.classList.add('sorted'); }); } // Initialize generateArray(); document.querySelector('.algorithm-btn').click(); // Re-render on window resize let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { renderArray(); }, 250); });