using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Media; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace SortVisualizer; public class VisualizerForm : Form { int[] array = Array.Empty(); Random rand = new(); int delay = 10; bool sorting = false; ComboBox algoSelector; Button startButton, stopButton, shuffleButton; TrackBar speedSlider; CheckBox soundToggle; Label speedLabel, statusLabel; List algorithms; CancellationTokenSource? cts; Dictionary toneCache = new(); SemaphoreSlim soundSemaphore = new(1, 1); // prevent sound overlap int highlightedIndex1 = -1; int highlightedIndex2 = -1; public VisualizerForm() { Text = "C# Sorting"; DoubleBuffered = true; MinimumSize = new Size(800, 500); Width = 1000; Height = 600; algorithms = new() { new BubbleSort(), new InsertionSort(), new BogoSort(), new MergeSort() , new StoogeSort() }; // --- Top Control Panel --- var topPanel = new FlowLayoutPanel { Dock = DockStyle.Top, AutoSize = true, WrapContents = true, FlowDirection = FlowDirection.LeftToRight, Padding = new Padding(5), AutoScroll = true }; algoSelector = new ComboBox { Width = 160, DropDownStyle = ComboBoxStyle.DropDownList }; algoSelector.DataSource = algorithms; algoSelector.DisplayMember = "Name"; int buttonHeight = algoSelector.Height; startButton = new Button { Width = 100, Height = buttonHeight, Text = "Start" }; stopButton = new Button { Width = 100, Height = buttonHeight, Text = "Stop" }; shuffleButton = new Button { Width = 100, Height = buttonHeight, Text = "Shuffle" }; soundToggle = new CheckBox { Width = 100, Height = buttonHeight, Text = "Sound On", Checked = true }; speedLabel = new Label { Width = 60, Height = buttonHeight, Text = "Speed: 10", TextAlign = ContentAlignment.MiddleCenter }; speedSlider = new TrackBar { Width = 250, Minimum = 1, Maximum = 100, Value = 10, TickFrequency = 10, SmallChange = 1, LargeChange = 10, Height = buttonHeight }; statusLabel = new Label { Width = 150, Height = buttonHeight, Text = "Idle", TextAlign = ContentAlignment.MiddleRight }; topPanel.Controls.AddRange(new Control[] { algoSelector, startButton, stopButton, shuffleButton, soundToggle, speedLabel, speedSlider, statusLabel }); Controls.Add(topPanel); speedSlider.ValueChanged += (s, e) => { delay = 101 - speedSlider.Value; speedLabel.Text = $"Speed: {speedSlider.Value}"; }; startButton.Click += async (s, e) => await StartSort(); stopButton.Click += (s, e) => StopSort(); shuffleButton.Click += (s, e) => Shuffle(); this.Resize += (s, e) => Invalidate(); Shuffle(); } void Shuffle() { if (sorting) return; array = Enumerable.Range(1, 100).OrderBy(_ => rand.Next()).ToArray(); highlightedIndex1 = highlightedIndex2 = -1; Invalidate(); statusLabel.Text = "Shuffled!"; } async Task StartSort() { if (sorting) return; sorting = true; cts = new CancellationTokenSource(); var algo = (ISortAlgorithm)algoSelector.SelectedItem!; statusLabel.Text = $"Sorting ( {algo.Name} )..."; try { await algo.Sort( array, () => Invalidate(), // keep original signature delay, value => { if (soundToggle.Checked) _ = PlaySoundAsync(value); }, cts.Token ); highlightedIndex1 = highlightedIndex2 = -1; Invalidate(); if (!cts.Token.IsCancellationRequested) statusLabel.Text = "Done!"; } finally { sorting = false; } } void StopSort() { if (!sorting) return; cts?.Cancel(); sorting = false; highlightedIndex1 = highlightedIndex2 = -1; Invalidate(); statusLabel.Text = "Stopped."; } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); var g = e.Graphics; int offsetY = 50; int marginX = 20; if (array.Length == 0) return; int barWidth = (ClientSize.Width - 2 * marginX) / array.Length; int availableHeight = ClientSize.Height - offsetY; for (int i = 0; i < array.Length; i++) { int height = (int)(array[i] * (availableHeight / (float)array.Length)); Brush brush = Brushes.LightGreen; if (sorting) { if (i == highlightedIndex1) brush = Brushes.Red; else if (i == highlightedIndex2) brush = Brushes.Orange; else brush = Brushes.DeepSkyBlue; } g.FillRectangle(brush, marginX + i * barWidth, ClientSize.Height - height, barWidth - 1, height); } } async Task PlaySoundAsync(int value) { // Use semaphore to prevent overlapping sounds if (!await soundSemaphore.WaitAsync(0)) return; // skip if already playing try { int freq = 220 + (int)(value * 12.0); int durationMs = 10; if (!toneCache.TryGetValue(freq, out var wav)) { wav = GenerateToneWav(freq, durationMs, 44100); // Limit cache size if (toneCache.Count > 200) toneCache.Clear(); toneCache[freq] = wav; } await Task.Run(() => { try { using var ms = new MemoryStream(wav); using var player = new SoundPlayer(ms); player.PlaySync(); // blocks on background thread } catch { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Console.Beep(freq, durationMs); } catch { } } }); } finally { soundSemaphore.Release(); } } protected override void Dispose(bool disposing) { if (disposing) { cts?.Dispose(); soundSemaphore?.Dispose(); } base.Dispose(disposing); } #region Tone Generation public static byte[] GenerateToneWav(int frequency, int durationMs, int sampleRate = 44100, double amplitude = 0.25) { int samples = (int)((durationMs / 1000.0) * sampleRate); using var ms = new MemoryStream(); using var bw = new BinaryWriter(ms); short numChannels = 1; short bitsPerSample = 16; int byteRate = sampleRate * numChannels * bitsPerSample / 8; short blockAlign = (short)(numChannels * bitsPerSample / 8); int dataSize = samples * numChannels * (bitsPerSample / 8); bw.Write(System.Text.Encoding.ASCII.GetBytes("RIFF")); bw.Write(36 + dataSize); bw.Write(System.Text.Encoding.ASCII.GetBytes("WAVE")); bw.Write(System.Text.Encoding.ASCII.GetBytes("fmt ")); bw.Write(16); bw.Write((short)1); bw.Write(numChannels); bw.Write(sampleRate); bw.Write(byteRate); bw.Write(blockAlign); bw.Write(bitsPerSample); bw.Write(System.Text.Encoding.ASCII.GetBytes("data")); bw.Write(dataSize); double twoPiF = 2 * Math.PI * frequency; for (int n = 0; n < samples; n++) { double t = n / (double)sampleRate; double sample = amplitude * Math.Sin(twoPiF * t); short s = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, sample * short.MaxValue)); bw.Write(s); } bw.Flush(); return ms.ToArray(); } #endregion }