JavaScript · 26387 bytes Raw Blame History
1 let array = [];
2 let arraySize = 20;
3 let selectedAlgorithm = 'bubble';
4 let sorting = false;
5 let comparisons = 0;
6 let swaps = 0;
7 let attempts = 0;
8
9 const asciiChars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
10
11 // Audio context and sound system
12 let audioContext;
13 let isSoundEnabled = true;
14 let slideWhistleOscillator;
15 let slideWhistleGain;
16 let startTime;
17 let totalSortingTime = 10; // Will be dynamically adjusted
18
19 function initAudio() {
20 if (!audioContext) {
21 audioContext = new (window.AudioContext || window.webkitAudioContext)();
22 }
23 }
24
25 function startSlideWhistle() {
26 if (!isSoundEnabled || !audioContext) return;
27
28 // Create oscillator and gain for slide whistle
29 slideWhistleOscillator = audioContext.createOscillator();
30 slideWhistleGain = audioContext.createGain();
31
32 slideWhistleOscillator.type = 'sine';
33
34 // Start at low frequency
35 slideWhistleOscillator.frequency.setValueAtTime(200, audioContext.currentTime);
36
37 // Estimate sorting time based on array size and algorithm
38 totalSortingTime = estimateSortingTime();
39
40 // Schedule a smooth frequency ramp over the estimated time
41 slideWhistleOscillator.frequency.exponentialRampToValueAtTime(
42 800,
43 audioContext.currentTime + totalSortingTime
44 );
45
46 slideWhistleGain.gain.setValueAtTime(0.15, audioContext.currentTime);
47
48 slideWhistleOscillator.connect(slideWhistleGain);
49 slideWhistleGain.connect(audioContext.destination);
50
51 slideWhistleOscillator.start();
52 startTime = audioContext.currentTime;
53 }
54
55 function estimateSortingTime() {
56 // Estimate based on array size and algorithm complexity
57 const baseTime = array.length * getDelay() / 1000;
58
59 switch(selectedAlgorithm) {
60 case 'bubble':
61 case 'selection':
62 case 'insertion':
63 case 'cocktail':
64 return baseTime * array.length * 0.5;
65 case 'quick':
66 case 'merge':
67 case 'heap':
68 return baseTime * Math.log2(array.length) * 2;
69 case 'shell':
70 case 'comb':
71 return baseTime * array.length * 0.3;
72 case 'radix':
73 return baseTime * 3;
74 case 'gnome':
75 return baseTime * array.length * 0.7;
76 case 'pancake':
77 return baseTime * array.length * 0.8;
78 case 'bitonic':
79 return baseTime * Math.log2(array.length) * Math.log2(array.length);
80 case 'bogo':
81 return baseTime * 10; // Who knows!
82 default:
83 return baseTime * array.length * 0.5;
84 }
85 }
86
87 function updateSlideWhistle(progress) {
88 // With exponential ramping, we don't need to update manually
89 // The Web Audio API handles the smooth transition
90 }
91
92 function stopSlideWhistle() {
93 if (!slideWhistleOscillator) return;
94
95 // Cancel any scheduled frequency changes
96 slideWhistleOscillator.frequency.cancelScheduledValues(audioContext.currentTime);
97
98 // Set current frequency and fade out
99 const currentFreq = slideWhistleOscillator.frequency.value;
100 slideWhistleOscillator.frequency.setValueAtTime(currentFreq, audioContext.currentTime);
101
102 // Smooth fade out
103 slideWhistleGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.2);
104
105 setTimeout(() => {
106 if (slideWhistleOscillator) {
107 slideWhistleOscillator.stop();
108 slideWhistleOscillator = null;
109 slideWhistleGain = null;
110 }
111 }, 200);
112 }
113
114 function playZoot() {
115 if (!isSoundEnabled || !audioContext) return;
116
117 // Create a fun "zoot" sound with multiple components
118 const oscillator1 = audioContext.createOscillator();
119 const oscillator2 = audioContext.createOscillator();
120 const oscillator3 = audioContext.createOscillator();
121 const gainNode = audioContext.createGain();
122 const filter = audioContext.createBiquadFilter();
123
124 // Three oscillators for a richer, more satisfying sound
125 oscillator1.type = 'sawtooth';
126 oscillator2.type = 'square';
127 oscillator3.type = 'triangle';
128
129 // Quick frequency sweep for "zoot" effect
130 const now = audioContext.currentTime;
131
132 // Main sweep
133 oscillator1.frequency.setValueAtTime(800, now);
134 oscillator1.frequency.exponentialRampToValueAtTime(1600, now + 0.05);
135 oscillator1.frequency.exponentialRampToValueAtTime(400, now + 0.15);
136 oscillator1.frequency.exponentialRampToValueAtTime(800, now + 0.25);
137
138 // Harmonic sweep
139 oscillator2.frequency.setValueAtTime(400, now);
140 oscillator2.frequency.exponentialRampToValueAtTime(800, now + 0.05);
141 oscillator2.frequency.exponentialRampToValueAtTime(200, now + 0.15);
142 oscillator2.frequency.exponentialRampToValueAtTime(400, now + 0.25);
143
144 // Sub bass sweep
145 oscillator3.frequency.setValueAtTime(100, now);
146 oscillator3.frequency.exponentialRampToValueAtTime(200, now + 0.1);
147 oscillator3.frequency.exponentialRampToValueAtTime(50, now + 0.25);
148
149 // Filter sweep for extra "zoot" character
150 filter.type = 'bandpass';
151 filter.frequency.setValueAtTime(400, now);
152 filter.frequency.exponentialRampToValueAtTime(2000, now + 0.1);
153 filter.frequency.exponentialRampToValueAtTime(800, now + 0.25);
154 filter.Q.value = 5;
155
156 // Envelope
157 gainNode.gain.setValueAtTime(0, now);
158 gainNode.gain.linearRampToValueAtTime(0.4, now + 0.02);
159 gainNode.gain.linearRampToValueAtTime(0.3, now + 0.1);
160 gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
161
162 // Connect
163 oscillator1.connect(filter);
164 oscillator2.connect(filter);
165 oscillator3.connect(filter);
166 filter.connect(gainNode);
167 gainNode.connect(audioContext.destination);
168
169 // Play
170 oscillator1.start();
171 oscillator2.start();
172 oscillator3.start();
173 oscillator1.stop(now + 0.4);
174 oscillator2.stop(now + 0.4);
175 oscillator3.stop(now + 0.4);
176 }
177
178 // Add a function to calculate sorting progress
179 function getSortingProgress() {
180 let sortedCount = 0;
181 const rows = document.querySelectorAll('.bar-row');
182 rows.forEach(row => {
183 if (row.classList.contains('sorted')) {
184 sortedCount++;
185 }
186 });
187 return sortedCount / array.length;
188 }
189
190 // Toggle sound on/off
191 function toggleSound() {
192 isSoundEnabled = !isSoundEnabled;
193 const soundBtn = document.getElementById('soundBtn');
194 soundBtn.textContent = isSoundEnabled ? 'SOUND: ON' : 'SOUND: OFF';
195
196 // If sound is turned off while playing, stop the whistle
197 if (!isSoundEnabled && slideWhistleOscillator) {
198 stopSlideWhistle();
199 }
200 }
201
202 function generateArray() {
203 // Use smaller array for Bogo Sort
204 if (selectedAlgorithm === 'bogo') {
205 arraySize = 5; // Much smaller for Bogo!
206 document.getElementById('arraySize').textContent = arraySize;
207 } else if (arraySize === 5 && selectedAlgorithm !== 'bogo') {
208 arraySize = 20; // Reset to normal size
209 document.getElementById('arraySize').textContent = arraySize;
210 }
211
212 array = [];
213 for (let i = 0; i < arraySize; i++) {
214 array.push(Math.floor(Math.random() * 90) + 10);
215 }
216 renderArray();
217 resetStats();
218 }
219
220 function getAsciiBar(value, maxValue) {
221 const normalized = value / maxValue;
222 const maxBarLength = window.innerWidth < 768 ? 20 : 40;
223 const barLength = Math.floor(normalized * maxBarLength);
224 let bar = '';
225
226 for (let i = 0; i < barLength; i++) {
227 bar += asciiChars[Math.min(7, Math.floor(i / 5))];
228 }
229
230 return bar;
231 }
232
233 function renderArray() {
234 const container = document.getElementById('visualization');
235 const maxValue = Math.max(...array);
236 const isMobile = window.innerWidth < 768;
237 const borderLength = isMobile ? 30 : 50;
238 const padLength = isMobile ? 20 : 40;
239 let asciiDisplay = '';
240
241 // Top border
242 asciiDisplay += '┌' + '─'.repeat(borderLength) + '┐\n';
243
244 // Array visualization
245 array.forEach((value, index) => {
246 const bar = getAsciiBar(value, maxValue);
247 const paddedValue = String(value).padStart(3, '0');
248 const paddedIndex = String(index).padStart(2, '0');
249 if (isMobile) {
250 asciiDisplay += `│${paddedIndex}:[${paddedValue}]${bar.padEnd(padLength, ' ')}│\n`;
251 } else {
252 asciiDisplay += `│ ${paddedIndex}: [${paddedValue}] ${bar.padEnd(padLength, ' ')}│\n`;
253 }
254 });
255
256 // Bottom border
257 asciiDisplay += '└' + '─'.repeat(borderLength) + '┘';
258
259 container.innerHTML = asciiDisplay.split('\n').map((line, index) => {
260 if (index > 0 && index <= array.length) {
261 return `<span class="bar-row" data-index="${index-1}">${line}</span>`;
262 }
263 return `<span>${line}</span>`;
264 }).join('\n');
265 }
266
267 function resetStats() {
268 comparisons = 0;
269 swaps = 0;
270 attempts = 0;
271 updateStats();
272 }
273
274 function updateStats() {
275 document.getElementById('comparisons').textContent = String(comparisons).padStart(4, '0');
276 document.getElementById('swaps').textContent = String(swaps).padStart(4, '0');
277 document.getElementById('attempts').textContent = String(attempts).padStart(4, '0');
278 }
279
280 function selectAlgorithm(algo) {
281 selectedAlgorithm = algo;
282 document.querySelectorAll('.algorithm-btn').forEach(btn => {
283 btn.classList.remove('active');
284 });
285 event.target.classList.add('active');
286
287 // Show/hide attempts counter for Bogo Sort
288 const attemptsContainer = document.getElementById('attemptsContainer');
289 if (algo === 'bogo') {
290 attemptsContainer.style.display = 'block';
291 generateArray(); // Auto-generate smaller array for Bogo
292 } else {
293 attemptsContainer.style.display = 'none';
294 if (arraySize === 5) {
295 generateArray(); // Reset to normal size
296 }
297 }
298 }
299
300 async function startSort() {
301 if (sorting) return;
302
303 sorting = true;
304 document.getElementById('startBtn').disabled = true;
305 resetStats();
306
307 // Initialize audio on user interaction
308 initAudio();
309
310 // Start slide whistle
311 startSlideWhistle();
312
313 switch (selectedAlgorithm) {
314 case 'bubble':
315 await bubbleSort();
316 break;
317 case 'selection':
318 await selectionSort();
319 break;
320 case 'insertion':
321 await insertionSort();
322 break;
323 case 'quick':
324 await quickSort(0, array.length - 1);
325 break;
326 case 'merge':
327 await mergeSort(0, array.length - 1);
328 break;
329 case 'heap':
330 await heapSort();
331 break;
332 case 'shell':
333 await shellSort();
334 break;
335 case 'cocktail':
336 await cocktailSort();
337 break;
338 case 'radix':
339 await radixSort();
340 break;
341 case 'gnome':
342 await gnomeSort();
343 break;
344 case 'bogo':
345 await bogoSort();
346 break;
347 case 'comb':
348 await combSort();
349 break;
350 case 'pancake':
351 await pancakeSort();
352 break;
353 case 'bitonic':
354 await bitonicSort();
355 break;
356 }
357
358 // Stop slide whistle and play zoot
359 stopSlideWhistle();
360 playZoot();
361
362 markAllSorted();
363 sorting = false;
364 document.getElementById('startBtn').disabled = false;
365 }
366
367 function getDelay() {
368 return 101 - document.getElementById('speedSlider').value;
369 }
370
371 async function sleep(ms) {
372 return new Promise(resolve => setTimeout(resolve, ms));
373 }
374
375 async function compare(i, j) {
376 comparisons++;
377 updateStats();
378 const rows = document.querySelectorAll('.bar-row');
379 if (rows[i]) rows[i].classList.add('comparing');
380 if (rows[j]) rows[j].classList.add('comparing');
381
382 // Subtle pitch update during comparisons for continuous glissando
383 const progress = getSortingProgress();
384 updateSlideWhistle(progress);
385
386 await sleep(getDelay());
387 if (rows[i]) rows[i].classList.remove('comparing');
388 if (rows[j]) rows[j].classList.remove('comparing');
389 }
390
391 async function swap(i, j) {
392 swaps++;
393 updateStats();
394
395 const temp = array[i];
396 array[i] = array[j];
397 array[j] = temp;
398
399 renderArray();
400
401 // Update slide whistle pitch based on progress
402 const progress = getSortingProgress();
403 updateSlideWhistle(progress);
404
405 await sleep(getDelay());
406 }
407
408 async function bubbleSort() {
409 for (let i = 0; i < array.length - 1; i++) {
410 for (let j = 0; j < array.length - i - 1; j++) {
411 await compare(j, j + 1);
412 if (array[j] > array[j + 1]) {
413 await swap(j, j + 1);
414 }
415 }
416 document.querySelectorAll('.bar-row')[array.length - i - 1].classList.add('sorted');
417
418 // Update slide whistle pitch
419 const progress = getSortingProgress();
420 updateSlideWhistle(progress);
421 }
422 document.querySelectorAll('.bar-row')[0].classList.add('sorted');
423 }
424
425 async function selectionSort() {
426 for (let i = 0; i < array.length - 1; i++) {
427 let minIdx = i;
428 for (let j = i + 1; j < array.length; j++) {
429 await compare(minIdx, j);
430 if (array[j] < array[minIdx]) {
431 minIdx = j;
432 }
433 }
434 if (minIdx !== i) {
435 await swap(i, minIdx);
436 }
437 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
438
439 // Update slide whistle pitch
440 const progress = getSortingProgress();
441 updateSlideWhistle(progress);
442 }
443 document.querySelectorAll('.bar-row')[array.length - 1].classList.add('sorted');
444 }
445
446 async function insertionSort() {
447 for (let i = 1; i < array.length; i++) {
448 let j = i - 1;
449 let key = array[i];
450
451 while (j >= 0 && array[j] > key) {
452 await compare(j, j + 1);
453 array[j + 1] = array[j];
454 renderArray();
455 j--;
456 await sleep(getDelay());
457 }
458
459 array[j + 1] = key;
460 renderArray();
461 document.querySelectorAll('.bar-row')[j + 1].classList.add('sorted');
462 }
463 markAllSorted();
464 }
465
466 async function quickSort(low, high) {
467 if (low < high) {
468 let pi = await partition(low, high);
469 await quickSort(low, pi - 1);
470 await quickSort(pi + 1, high);
471 }
472 }
473
474 async function partition(low, high) {
475 let pivot = array[high];
476 let i = low - 1;
477
478 for (let j = low; j < high; j++) {
479 await compare(j, high);
480 if (array[j] < pivot) {
481 i++;
482 await swap(i, j);
483 }
484 }
485
486 await swap(i + 1, high);
487 return i + 1;
488 }
489
490 async function mergeSort(left, right) {
491 if (left < right) {
492 let mid = Math.floor((left + right) / 2);
493 await mergeSort(left, mid);
494 await mergeSort(mid + 1, right);
495 await merge(left, mid, right);
496 }
497 }
498
499 async function merge(left, mid, right) {
500 let leftArr = array.slice(left, mid + 1);
501 let rightArr = array.slice(mid + 1, right + 1);
502 let i = 0, j = 0, k = left;
503
504 while (i < leftArr.length && j < rightArr.length) {
505 await compare(left + i, mid + 1 + j);
506 if (leftArr[i] <= rightArr[j]) {
507 array[k] = leftArr[i];
508 i++;
509 } else {
510 array[k] = rightArr[j];
511 j++;
512 }
513 k++;
514 renderArray();
515 await sleep(getDelay());
516 }
517
518 while (i < leftArr.length) {
519 array[k] = leftArr[i];
520 i++;
521 k++;
522 renderArray();
523 await sleep(getDelay());
524 }
525
526 while (j < rightArr.length) {
527 array[k] = rightArr[j];
528 j++;
529 k++;
530 renderArray();
531 await sleep(getDelay());
532 }
533 }
534
535 // Heap Sort
536 async function heapSort() {
537 // Build max heap
538 for (let i = Math.floor(array.length / 2) - 1; i >= 0; i--) {
539 await heapify(array.length, i);
540 }
541
542 // Extract elements from heap one by one
543 for (let i = array.length - 1; i > 0; i--) {
544 await swap(0, i);
545 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
546 await heapify(i, 0);
547
548 // Update slide whistle pitch
549 const progress = getSortingProgress();
550 updateSlideWhistle(progress);
551 }
552 document.querySelectorAll('.bar-row')[0].classList.add('sorted');
553 }
554
555 async function heapify(n, i) {
556 let largest = i;
557 let left = 2 * i + 1;
558 let right = 2 * i + 2;
559
560 if (left < n) {
561 await compare(left, largest);
562 if (array[left] > array[largest]) {
563 largest = left;
564 }
565 }
566
567 if (right < n) {
568 await compare(right, largest);
569 if (array[right] > array[largest]) {
570 largest = right;
571 }
572 }
573
574 if (largest !== i) {
575 await swap(i, largest);
576 await heapify(n, largest);
577 }
578 }
579
580 // Shell Sort
581 async function shellSort() {
582 let gap = Math.floor(array.length / 2);
583
584 while (gap > 0) {
585 for (let i = gap; i < array.length; i++) {
586 let temp = array[i];
587 let j = i;
588
589 while (j >= gap) {
590 await compare(j - gap, j);
591 if (array[j - gap] > temp) {
592 array[j] = array[j - gap];
593 j -= gap;
594 renderArray();
595 await sleep(getDelay());
596 } else {
597 break;
598 }
599 }
600
601 array[j] = temp;
602 renderArray();
603 await sleep(getDelay());
604 }
605
606 gap = Math.floor(gap / 2);
607 }
608
609 // Mark all as sorted
610 for (let i = 0; i < array.length; i++) {
611 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
612 }
613 }
614
615 // Cocktail Sort (Bidirectional Bubble Sort)
616 async function cocktailSort() {
617 let swapped = true;
618 let start = 0;
619 let end = array.length - 1;
620
621 while (swapped) {
622 swapped = false;
623
624 // Forward pass (like bubble sort)
625 for (let i = start; i < end; i++) {
626 await compare(i, i + 1);
627 if (array[i] > array[i + 1]) {
628 await swap(i, i + 1);
629 swapped = true;
630 }
631 }
632
633 if (!swapped) break;
634
635 document.querySelectorAll('.bar-row')[end].classList.add('sorted');
636 end--;
637 swapped = false;
638
639 // Backward pass
640 for (let i = end; i > start; i--) {
641 await compare(i, i - 1);
642 if (array[i] < array[i - 1]) {
643 await swap(i, i - 1);
644 swapped = true;
645 }
646 }
647
648 document.querySelectorAll('.bar-row')[start].classList.add('sorted');
649 start++;
650 }
651
652 // Mark remaining elements as sorted
653 for (let i = start; i <= end; i++) {
654 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
655 }
656 }
657
658 // Radix Sort (LSD - Least Significant Digit)
659 async function radixSort() {
660 // Find the maximum number to know number of digits
661 let max = Math.max(...array);
662
663 // Do counting sort for every digit
664 for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) {
665 await countingSortByDigit(exp);
666 }
667
668 // Mark all as sorted
669 for (let i = 0; i < array.length; i++) {
670 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
671 }
672 }
673
674 async function countingSortByDigit(exp) {
675 let output = new Array(array.length);
676 let count = new Array(10).fill(0);
677
678 // Store count of occurrences
679 for (let i = 0; i < array.length; i++) {
680 let digit = Math.floor(array[i] / exp) % 10;
681 count[digit]++;
682 }
683
684 // Change count[i] so it contains actual position
685 for (let i = 1; i < 10; i++) {
686 count[i] += count[i - 1];
687 }
688
689 // Build the output array
690 for (let i = array.length - 1; i >= 0; i--) {
691 let digit = Math.floor(array[i] / exp) % 10;
692 output[count[digit] - 1] = array[i];
693 count[digit]--;
694
695 // Visual comparison for the digit
696 if (i < array.length - 1) {
697 await compare(i, i + 1);
698 }
699 }
700
701 // Copy the output array to arr[]
702 for (let i = 0; i < array.length; i++) {
703 array[i] = output[i];
704 renderArray();
705 await sleep(getDelay());
706 }
707 }
708
709 // Gnome Sort (also called Stupid Sort)
710 async function gnomeSort() {
711 let index = 0;
712
713 while (index < array.length) {
714 if (index == 0) {
715 index++;
716 } else {
717 await compare(index, index - 1);
718 if (array[index] >= array[index - 1]) {
719 document.querySelectorAll('.bar-row')[index - 1].classList.add('sorted');
720 index++;
721 } else {
722 await swap(index, index - 1);
723 index--;
724 }
725 }
726 }
727
728 // Mark the last element as sorted
729 if (array.length > 0) {
730 document.querySelectorAll('.bar-row')[array.length - 1].classList.add('sorted');
731 }
732 }
733
734 // Bogo Sort (Warning: This is intentionally terrible!)
735 async function bogoSort() {
736 attempts = 0;
737 const maxAttempts = 10000; // Safety limit to prevent infinite loops
738
739 while (!isSorted() && attempts < maxAttempts) {
740 // Shuffle the array randomly
741 await shuffleArray();
742 attempts++;
743 updateStats();
744 }
745
746 if (attempts >= maxAttempts) {
747 alert("Bogo Sort gave up after " + maxAttempts + " attempts! Try a smaller array.");
748 }
749
750 // Mark all as sorted
751 for (let i = 0; i < array.length; i++) {
752 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
753 }
754 }
755
756 function isSorted() {
757 for (let i = 0; i < array.length - 1; i++) {
758 if (array[i] > array[i + 1]) {
759 return false;
760 }
761 }
762 return true;
763 }
764
765 async function shuffleArray() {
766 // Fisher-Yates shuffle with visualization
767 for (let i = array.length - 1; i > 0; i--) {
768 let j = Math.floor(Math.random() * (i + 1));
769 await compare(i, j);
770 await swap(i, j);
771 }
772 }
773
774 function markAllSorted() {
775 document.querySelectorAll('.bar-row').forEach(row => {
776 row.classList.add('sorted');
777 });
778 }
779
780 // Comb Sort (improved Bubble Sort with gap)
781 async function combSort() {
782 let gap = array.length;
783 let shrink = 1.3;
784 let sorted = false;
785
786 while (gap > 1 || !sorted) {
787 // Update gap
788 gap = Math.floor(gap / shrink);
789 if (gap < 1) gap = 1;
790
791 sorted = true;
792
793 // Single pass with current gap
794 for (let i = 0; i + gap < array.length; i++) {
795 await compare(i, i + gap);
796 if (array[i] > array[i + gap]) {
797 await swap(i, i + gap);
798 sorted = false;
799 }
800 }
801
802 // Mark sorted elements when gap is 1 (final pass)
803 if (gap === 1 && sorted) {
804 for (let i = 0; i < array.length; i++) {
805 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
806 await sleep(getDelay() / 4);
807 }
808 }
809 }
810 }
811
812 // Pancake Sort (flip sort)
813 async function pancakeSort() {
814 for (let curr_size = array.length; curr_size > 1; curr_size--) {
815 // Find index of maximum element in unsorted portion
816 let maxIdx = 0;
817 for (let i = 1; i < curr_size; i++) {
818 await compare(i, maxIdx);
819 if (array[i] > array[maxIdx]) {
820 maxIdx = i;
821 }
822 }
823
824 // Move maximum element to end if it's not already there
825 if (maxIdx !== curr_size - 1) {
826 // Flip to move max to beginning
827 if (maxIdx !== 0) {
828 await flip(maxIdx + 1);
829 }
830
831 // Flip to move max to correct position
832 await flip(curr_size);
833 }
834
835 document.querySelectorAll('.bar-row')[curr_size - 1].classList.add('sorted');
836 }
837 document.querySelectorAll('.bar-row')[0].classList.add('sorted');
838 }
839
840 // Helper function for pancake sort - flips array from 0 to n-1
841 async function flip(n) {
842 let start = 0;
843 let end = n - 1;
844
845 while (start < end) {
846 await swap(start, end);
847 start++;
848 end--;
849 }
850 }
851
852 // Bitonic Sort (requires power of 2 array size)
853 async function bitonicSort() {
854 // Adjust array size to nearest power of 2 if needed
855 const originalLength = array.length;
856 const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(array.length)));
857
858 // Pad with large values if necessary
859 while (array.length < nextPowerOf2) {
860 array.push(999);
861 }
862 renderArray();
863
864 // Start the bitonic sort
865 await bitonicSortRecursive(0, array.length, true);
866
867 // Remove padding if we added any
868 if (array.length > originalLength) {
869 array.length = originalLength;
870 renderArray();
871 }
872
873 // Mark all as sorted
874 for (let i = 0; i < originalLength; i++) {
875 document.querySelectorAll('.bar-row')[i].classList.add('sorted');
876 }
877 }
878
879 async function bitonicSortRecursive(lo, cnt, dir) {
880 if (cnt > 1) {
881 let k = Math.floor(cnt / 2);
882
883 // Sort in ascending order
884 await bitonicSortRecursive(lo, k, true);
885
886 // Sort in descending order
887 await bitonicSortRecursive(lo + k, k, false);
888
889 // Merge the whole sequence
890 await bitonicMerge(lo, cnt, dir);
891 }
892 }
893
894 async function bitonicMerge(lo, cnt, dir) {
895 if (cnt > 1) {
896 let k = Math.floor(cnt / 2);
897
898 for (let i = lo; i < lo + k; i++) {
899 await compare(i, i + k);
900
901 // Swap if needed based on direction
902 if ((array[i] > array[i + k]) === dir) {
903 await swap(i, i + k);
904 }
905 }
906
907 await bitonicMerge(lo, k, dir);
908 await bitonicMerge(lo + k, k, dir);
909 }
910 }
911
912 // Initialize
913 generateArray();
914 document.querySelector('.algorithm-btn').click();
915
916 // Re-render on window resize
917 let resizeTimeout;
918 window.addEventListener('resize', () => {
919 clearTimeout(resizeTimeout);
920 resizeTimeout = setTimeout(() => {
921 renderArray();
922 }, 250);
923 });