Files
INF6B/simulations/astar/astar/Program.cs

316 lines
9.8 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;
namespace MazePathfinding
{
public class Cell
{
public int Row { get; set; }
public int Col { get; set; }
public int X { get; set; }
public int Y { get; set; }
public bool[] Walls { get; set; } = new bool[4]; // top, right, bottom, left
public bool Visited { get; set; }
public Cell(int row, int col, int cellSize)
{
Row = row;
Col = col;
X = col * cellSize;
Y = row * cellSize;
for (int i = 0; i < 4; i++) Walls[i] = true;
Visited = false;
}
public void Draw(Graphics g, int cellSize)
{
if (Visited)
{
using (var brush = new SolidBrush(Color.White))
g.FillRectangle(brush, X, Y, cellSize, cellSize);
}
using (var pen = new Pen(Color.Black, 2))
{
if (Walls[0]) g.DrawLine(pen, X, Y, X + cellSize, Y);
if (Walls[1]) g.DrawLine(pen, X + cellSize, Y, X + cellSize, Y + cellSize);
if (Walls[2]) g.DrawLine(pen, X + cellSize, Y + cellSize, X, Y + cellSize);
if (Walls[3]) g.DrawLine(pen, X, Y + cellSize, X, Y);
}
}
public void Highlight(Graphics g, Color color, int cellSize)
{
using (var brush = new SolidBrush(color))
g.FillRectangle(brush, X, Y, cellSize, cellSize);
}
}
public class MazeForm : Form
{
private const int WIDTH = 600;
private int rows = 20;
private int cellSize;
private Cell[,] grid;
private Cell start;
private Cell end;
private List<Cell> path = new List<Cell>();
private HashSet<Cell> openSet = new HashSet<Cell>();
private System.Windows.Forms.Timer timer;
private bool isSearching = false;
public MazeForm()
{
Text = "A* Maze Pathfinding - Press SPACE to solve, UP/DOWN to resize";
ClientSize = new Size(WIDTH, WIDTH);
DoubleBuffered = true;
KeyPreview = true;
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
cellSize = WIDTH / rows;
CreateMaze(rows);
timer = new System.Windows.Forms.Timer { Interval = 16 };
timer.Tick += (s, e) => Invalidate();
timer.Start();
this.KeyDown += MazeForm_KeyDown;
this.Paint += MazeForm_Paint;
}
private void CreateMaze(int r)
{
rows = r;
cellSize = WIDTH / rows;
grid = new Cell[rows, rows];
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
grid[i, j] = new Cell(i, j, cellSize);
GenerateMaze(grid[0, 0]);
start = grid[0, 0];
end = grid[rows - 1, rows - 1];
path.Clear();
openSet.Clear();
}
private void GenerateMaze(Cell current)
{
var stack = new Stack<Cell>();
current.Visited = true;
var random = new Random();
while (true)
{
var neighbors = new List<Cell>();
int r = current.Row, c = current.Col;
if (r > 0 && !grid[r - 1, c].Visited) neighbors.Add(grid[r - 1, c]);
if (r < rows - 1 && !grid[r + 1, c].Visited) neighbors.Add(grid[r + 1, c]);
if (c > 0 && !grid[r, c - 1].Visited) neighbors.Add(grid[r, c - 1]);
if (c < rows - 1 && !grid[r, c + 1].Visited) neighbors.Add(grid[r, c + 1]);
if (neighbors.Count > 0)
{
var nextCell = neighbors[random.Next(neighbors.Count)];
stack.Push(current);
RemoveWalls(current, nextCell);
nextCell.Visited = true;
current = nextCell;
}
else if (stack.Count > 0)
{
current = stack.Pop();
}
else
{
break;
}
}
}
private void RemoveWalls(Cell a, Cell b)
{
int dx = a.Col - b.Col;
int dy = a.Row - b.Row;
if (dx == 1)
{
a.Walls[3] = false;
b.Walls[1] = false;
}
else if (dx == -1)
{
a.Walls[1] = false;
b.Walls[3] = false;
}
if (dy == 1)
{
a.Walls[0] = false;
b.Walls[2] = false;
}
else if (dy == -1)
{
a.Walls[2] = false;
b.Walls[0] = false;
}
}
private async void MazeForm_KeyDown(object sender, KeyEventArgs e)
{
if (isSearching) return;
if (e.KeyCode == Keys.Space)
{
isSearching = true;
path = await Task.Run(() => AStar(start, end));
isSearching = false;
Invalidate();
}
else if (e.KeyCode == Keys.Up)
{
CreateMaze(rows + 1);
Invalidate();
}
else if (e.KeyCode == Keys.Down && rows > 5)
{
CreateMaze(rows - 1);
Invalidate();
}
}
private int Heuristic(Cell a, Cell b)
{
return Math.Abs(a.Row - b.Row) + Math.Abs(a.Col - b.Col);
}
private List<Cell> AStar(Cell startCell, Cell endCell)
{
var openSetQueue = new SortedSet<(int f, int count, Cell cell)>(
Comparer<(int f, int count, Cell cell)>.Create((a, b) =>
{
int cmp = a.f.CompareTo(b.f);
return cmp != 0 ? cmp : a.count.CompareTo(b.count);
}));
int count = 0;
openSetQueue.Add((0, count, startCell));
var cameFrom = new Dictionary<Cell, Cell>();
var gScore = new Dictionary<Cell, int>();
var fScore = new Dictionary<Cell, int>();
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
{
gScore[grid[i, j]] = int.MaxValue;
fScore[grid[i, j]] = int.MaxValue;
}
gScore[startCell] = 0;
fScore[startCell] = Heuristic(startCell, endCell);
openSet.Clear();
openSet.Add(startCell);
while (openSetQueue.Count > 0)
{
var current = openSetQueue.Min.cell;
openSetQueue.Remove(openSetQueue.Min);
openSet.Remove(current);
if (current == endCell)
{
var pathResult = new List<Cell>();
while (cameFrom.ContainsKey(current))
{
current = cameFrom[current];
pathResult.Add(current);
}
openSet.Clear();
return pathResult;
}
foreach (var neighbor in GetNeighbors(current))
{
int tempG = gScore[current] + 1;
if (tempG < gScore[neighbor])
{
cameFrom[neighbor] = current;
gScore[neighbor] = tempG;
fScore[neighbor] = tempG + Heuristic(neighbor, endCell);
if (!openSet.Contains(neighbor))
{
count++;
openSetQueue.Add((fScore[neighbor], count, neighbor));
openSet.Add(neighbor);
}
}
}
try
{
Invoke(new Action(() => Invalidate()));
}
catch { }
Thread.Sleep(100);
}
openSet.Clear();
return new List<Cell>();
}
private List<Cell> GetNeighbors(Cell cell)
{
var neighbors = new List<Cell>();
int r = cell.Row, c = cell.Col;
if (!cell.Walls[0] && r > 0) neighbors.Add(grid[r - 1, c]);
if (!cell.Walls[1] && c < rows - 1) neighbors.Add(grid[r, c + 1]);
if (!cell.Walls[2] && r < rows - 1) neighbors.Add(grid[r + 1, c]);
if (!cell.Walls[3] && c > 0) neighbors.Add(grid[r, c - 1]);
return neighbors;
}
private void MazeForm_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.Clear(Color.White);
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
grid[i, j].Draw(g, cellSize);
foreach (var cell in openSet)
cell.Highlight(g, Color.LimeGreen, cellSize);
foreach (var cell in path)
cell.Highlight(g, Color.Yellow, cellSize);
start.Highlight(g, Color.Blue, cellSize);
end.Highlight(g, Color.Red, cellSize);
// Redraw walls over highlights
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
grid[i, j].Draw(g, cellSize);
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MazeForm());
}
}
}