271 lines
7.3 KiB
C#
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);
|
|
}
|
|
}
|
|
} |