Files
INF6B/simulations/SortVisualizer/VisualizerForm.cs

271 lines
7.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using NAudio.Wave;
namespace SortVisualizer;
public class VisualizerForm : Form
{
int[] array = Array.Empty<int>();
Random rand = new();
int delay = 10;
bool sorting = false;
ComboBox algoSelector;
Button startButton, stopButton, shuffleButton;
TrackBar speedSlider;
CheckBox soundToggle;
List<ISortAlgorithm> algorithms;
CancellationTokenSource? cts;
// Audio
BufferedWaveProvider? waveProvider;
WaveOutEvent? waveOut;
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(), new RadixSort(),
new BeadSort()
};
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 };
speedSlider = new TrackBar
{
Width = 250,
Minimum = 1,
Maximum = 100,
Value = 10,
TickFrequency = 10,
SmallChange = 1,
LargeChange = 10,
Height = buttonHeight
};
topPanel.Controls.AddRange(new Control[]
{
algoSelector, startButton, stopButton, shuffleButton,
soundToggle, speedSlider
});
Controls.Add(topPanel);
speedSlider.ValueChanged += (s, e) =>
{
delay = 101 - 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();
}
async Task StartSort()
{
if (sorting) return;
sorting = true;
cts = new CancellationTokenSource();
var algo = algoSelector.SelectedItem as ISortAlgorithm;
if (algo == null)
{
sorting = false;
return;
}
try
{
if (soundToggle.Checked)
{
InitializeAudio();
if (waveProvider != null)
{
_ = Task.Run(() => AudioLoopAsync(waveProvider, cts.Token));
}
}
await algo.Sort(
array,
() => Invalidate(),
delay,
value => { highlightedIndex1 = value; },
cts.Token
);
highlightedIndex1 = highlightedIndex2 = -1;
Invalidate();
}
finally
{
sorting = false;
StopAudio();
}
}
void StopSort()
{
if (!sorting) return;
cts?.Cancel();
sorting = false;
highlightedIndex1 = highlightedIndex2 = -1;
Invalidate();
StopAudio();
}
void InitializeAudio()
{
waveProvider = new BufferedWaveProvider(new WaveFormat(44100, 16, 1))
{
BufferDuration = TimeSpan.FromSeconds(5)
};
waveOut = new WaveOutEvent();
waveOut.Init(waveProvider);
waveOut.Play();
}
void StopAudio()
{
waveOut?.Stop();
waveOut?.Dispose();
waveOut = null;
waveProvider = null;
}
async Task AudioLoopAsync(BufferedWaveProvider provider, CancellationToken token)
{
short amplitude = 8000;
int sampleRate = 44100;
int chunkSamples = 512;
var buffer = new byte[chunkSamples * 2];
double phase = 0;
while (!token.IsCancellationRequested)
{
// Calculate frequency based on highlighted element or average
int freq = 220;
if (highlightedIndex1 >= 0 && highlightedIndex1 < array.Length)
{
freq = 220 + array[highlightedIndex1] * 15; // Rising pitch based on value
}
else if (array.Length > 0)
{
freq = 220 + (int)array.Average() * 15;
}
for (int n = 0; n < chunkSamples; n++)
{
double sample = Math.Sin(phase);
phase += 2 * Math.PI * freq / sampleRate;
// Keep phase in reasonable range
if (phase > 2 * Math.PI * 1000)
phase -= 2 * Math.PI * 1000;
short s = (short)(sample * amplitude);
buffer[n * 2] = (byte)(s & 0xFF);
buffer[n * 2 + 1] = (byte)((s >> 8) & 0xFF);
}
int space = provider.BufferLength - provider.BufferedBytes;
if (space >= buffer.Length)
{
provider.AddSamples(buffer, 0, buffer.Length);
}
else if (space > 0)
{
provider.AddSamples(buffer, buffer.Length - space, space);
}
await Task.Delay(1, token);
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
int offsetY = 50;
int marginX = 20;
int paddingTop = 5;
int paddingBottom = 5;
if (array.Length == 0) return;
int barWidth = (ClientSize.Width - 2 * marginX) / array.Length;
int top = offsetY + paddingTop;
int bottom = ClientSize.Height - paddingBottom;
int availableHeight = bottom - top;
int maxVal = array.Max();
for (int i = 0; i < array.Length; i++)
{
int height = (int)Math.Round(array[i] * (availableHeight / (float)maxVal));
Brush brush = Brushes.LightGreen;
if (sorting)
{
if (i == highlightedIndex1) brush = Brushes.Red;
else if (i == highlightedIndex2) brush = Brushes.Orange;
else brush = Brushes.DeepSkyBlue;
}
int y = bottom - height;
if (y < top) y = top;
g.FillRectangle(brush, marginX + i * barWidth, y, barWidth - 1, height);
}
}
}