2024-03-04 18:41:55 +02:00

480 lines
11 KiB
V

module main
import gg // actual graphics lib
import gx // lib have some constants like colors
import math // for math related function
const window_width = 800
const window_height = 800
const nrows = 50
// app struct that has property of current windows
struct App {
mut:
gg &gg.Context = unsafe { nil }
ui Ui
grid [][]Cell
start Point // start point of algorithm
end Point // end point or target point
}
// this needed to get the width and mouse position part of gg window
struct Ui {
mut:
dpi_scale f32
}
// struct for a point
struct Point {
mut:
x int
y int
}
/*
RED -> Closed
GREEN -> Open
BLACK -> Barrier
WHITE -> Empty
ORANGE -> Start
TURQOIISE -> End
PINK -> Path
*/
// struct for a cell of grid
struct Cell {
mut:
row int
col int
width int
pos Point
color gx.Color
flag int // 0->empty, 1-> closed, 2-> open, 3-> barrier, 4-> start, 5-> end, 6-> path
neighbors []Point
}
// this is a node for priority queue
struct Node {
mut:
f_score int
cell Point
count int
}
// Min heap or priority queue
struct MinHeap {
mut:
data []Node
}
// main function
fn main() {
// app variable
mut app := &App{}
// setting values of app
app.gg = gg.new_context(
bg_color: gx.black // background color
width: window_width // window width
height: window_height // window height
create_window: true // this will create a different window
window_title: 'A* Path finding algorithm visusalizer' // title of the window
frame_fn: frame // this is frame function update the frame
event_fn: on_event // it calls on every event
init_fn: init_images // run at start of application
user_data: app // store user data
)
mut grid := initialise_grid() // initialize the grid variable and populate the matrix with each cell as empty
app.grid = grid // set grid to app attribute so you can access it by just passing app variable or with method of app
app.ui.dpi_scale = 1.0 // set scale this is use to make it responsive
app.start = &Point{ // set start point to -1, -1
x: -1
y: -1
}
app.end = &Point{ // set end point to -1, -1
x: -1
y: -1
}
app.gg.run() // run the app loop
}
// this function will run for every frame actually in a loop
fn frame(mut app App) {
app.gg.begin()
draw_grid(mut app, mut app.grid)
draw_gridlines(mut app)
app.gg.end()
}
// this will run at start of app
fn init_images(mut app App) {
// app.resize()
return
}
// this will handle user event which is stored in gg.event variable
fn on_event(event &gg.Event, mut app App) {
match event.typ {
.mouse_down {
x := int(event.mouse_x / app.ui.dpi_scale)
y := int(event.mouse_y / app.ui.dpi_scale)
btn := event.mouse_button
app.handle_mouse_event(x, y, btn)
}
.key_down {
app.on_key_down(event.key_code)
}
else {}
}
}
// handle mouse event to make a cell either start point end point or barrier or to clear
fn (mut app App) handle_mouse_event(x int, y int, btn_type gg.MouseButton) {
gap := window_width / nrows
row := int(y / gap)
col := int(x / gap)
match btn_type {
.left {
if app.start.x == -1 && !(row == app.end.y && col == app.end.x) {
app.start.x = col
app.start.y = row
set_cell_type(mut app.grid, app.start.y, app.start.x, 'start')
} else if app.end.x == -1 && !(row == app.start.y && col == app.start.x) {
app.end.x = col
app.end.y = row
set_cell_type(mut app.grid, app.end.y, app.end.x, 'end')
} else if !(row == app.start.y && col == app.start.x) && !(row == app.end.y
&& col == app.end.x) {
set_cell_type(mut app.grid, row, col, 'barrier')
}
}
.right {
if row == app.start.y && col == app.start.x {
app.start.x = -1
app.start.y = -1
}
if row == app.end.y && col == app.end.x {
app.end.x = -1
app.end.y = -1
}
set_cell_type(mut app.grid, row, col, 'reset')
}
else {}
}
}
// handle keyboard interaction by user ''
fn (mut app App) on_key_down(key gg.KeyCode) {
match key {
.space {
if app.start.x == -1 || app.end.x == -1 {
println('Error: either start or end node is missing')
} else {
for row := 0; row < nrows; row++ {
for j := 0; j < nrows; j++ {
update_neighbors(mut app.grid, row, j)
}
}
new_start := &Point{
x: app.start.y
y: app.start.x
}
new_end := &Point{
x: app.end.y
y: app.end.x
}
astar_path_finding(mut app, mut app.grid, new_start, new_end)
}
}
.q {
app.gg.quit()
}
.c {
draw_grid(mut app, mut app.grid)
draw_gridlines(mut app)
mut grid := initialise_grid()
app.grid = grid // set grid to app attribute so you can access it by just passing app variable or with method of app
app.ui.dpi_scale = 1.0 // set scale this is use to make it responsive
app.start = &Point{ // set start point to -1, -1
x: -1
y: -1
}
app.end = &Point{ // set end point to -1, -1
x: -1
y: -1
}
}
else {}
}
}
// draw grid lines
fn draw_gridlines(mut app App) {
dx := window_width / nrows
dy := window_height / nrows
for i := 0; i < nrows; i++ {
// horizontal lines
app.gg.draw_line(0, i * dy, window_width, i * dy, gx.black)
// vertical lines
app.gg.draw_line(i * dx, 0, dx * i, window_height, gx.black)
}
}
// heuristic function(point manhatten distance) that calculate approximate cost to reach from a given point to end(target)
fn hf(p1 Point, p2 Point) int {
return math.abs(p1.x - p2.x) + math.abs(p1.y - p2.y)
}
// get the position of mouse in terms of which cells' row and column
fn get_clicked_pos(pos Point, rows int, width int) (int, int) {
x := pos.x
y := pos.y
row := y / rows
col := x / rows
return row, col
}
// initialize grid attribute of app
fn initialise_grid() [][]Cell {
mut grid := [][]Cell{len: nrows, init: []Cell{len: nrows}}
gap := window_width / nrows
for i := 0; i < nrows; i++ {
for j := 0; j < nrows; j++ {
grid[i][j] = &Cell{
row: i
col: j
width: gap
pos: &Point{
x: j * gap
y: i * gap
}
color: gx.white
flag: 0
}
}
}
return grid
}
// draw the cells of grid
fn draw_grid(mut app App, mut grid [][]Cell) {
for i := 0; i < nrows; i++ {
for j := 0; j < nrows; j++ {
pos := app.grid[i][j].pos
width := app.grid[i][j].width
color := app.grid[i][j].color
app.gg.draw_rect_filled(pos.x, pos.y, width, width, color)
}
}
}
// update the neighbor of each cell in which cell you can visit (if it is not barrier or end or start)
fn update_neighbors(mut grid [][]Cell, row int, col int) {
if row < nrows - 1 && grid[row + 1][col].flag != 3 {
grid[row][col].neighbors << &Point{
x: row + 1
y: col
}
}
if row > 0 && grid[row - 1][col].flag != 3 {
grid[row][col].neighbors << &Point{
x: row - 1
y: col
}
}
if col < nrows - 1 && grid[row][col + 1].flag != 3 {
grid[row][col].neighbors << &Point{
x: row
y: col + 1
}
}
if col > 0 && grid[row][col - 1].flag != 3 {
grid[row][col].neighbors << &Point{
x: row
y: col - 1
}
}
}
// construct the path after finding it shows as pink color
fn reconstruct_path(mut grid [][]Cell, mut came_from [][]Point, start Point, end Point) {
mut x := end.x
mut y := end.y
for !(x == -1 && y == -1) {
set_cell_type(mut grid, x, y, 'path')
x = came_from[x][y].x
y = came_from[x][y].y
}
}
// a* path finding algorithm
fn astar_path_finding(mut app App, mut grid [][]Cell, start Point, end Point) {
mut priority_queue := &MinHeap{}
mut g_score := [][]int{len: nrows, init: []int{len: nrows}}
mut f_score := [][]int{len: nrows, init: []int{len: nrows}}
mut came_from := [][]Point{len: nrows, init: []Point{len: nrows}}
for i := 0; i < nrows; i++ {
for j := 0; j < nrows; j++ {
g_score[i][j] = 1_000_000_000_00
f_score[i][j] = 1_000_000_000_00
came_from[i][j] = &Point{
x: -1
y: -1
}
}
}
g_score[start.x][start.y] = 0
f_score[start.x][start.y] = g_score[start.x][start.y] + hf(start, end)
priority_queue.insert(&Node{
f_score: f_score[start.x][start.y]
cell: &Point{
x: start.x
y: start.y
}
count: 0
})
for priority_queue.len() > 0 {
curr_node := priority_queue.pop() or {
panic('There is nothing in queue how did it reach here idk')
}
curr_pos := curr_node.cell
set_cell_type(mut grid, curr_pos.x, curr_pos.y, 'close')
if curr_pos.x == end.x && curr_pos.y == end.y {
set_cell_type(mut grid, start.x, start.y, 'start')
set_cell_type(mut grid, end.x, end.y, 'end')
came_from[end.x][end.y] = came_from[curr_pos.x][curr_pos.y]
reconstruct_path(mut grid, mut came_from, start, end)
set_cell_type(mut grid, start.x, start.y, 'start')
set_cell_type(mut grid, end.x, end.y, 'end')
return
}
for neighbor in grid[curr_pos.x][curr_pos.y].neighbors {
mut temp_g_score := g_score[curr_pos.x][curr_pos.y] + 1
if temp_g_score < g_score[neighbor.x][neighbor.y] {
g_score[neighbor.x][neighbor.y] = temp_g_score
if !(neighbor.x == start.x && neighbor.y == start.y) {
priority_queue.insert(&Node{
f_score: g_score[neighbor.x][neighbor.y] + hf(neighbor, end)
cell: neighbor
count: curr_node.count + 1
})
came_from[neighbor.x][neighbor.y] = curr_pos
set_cell_type(mut grid, neighbor.x, neighbor.y, 'open')
}
}
}
}
set_cell_type(mut grid, start.x, start.y, 'start')
}
// change the property of a cell
fn set_cell_type(mut grid [][]Cell, row int, col int, typ string) {
match typ {
'reset' {
grid[row][col].color = gx.white
grid[row][col].flag = 0
}
'close' {
grid[row][col].color = gx.red
grid[row][col].flag = 1
}
'open' {
grid[row][col].color = gx.green
grid[row][col].flag = 2
}
'barrier' {
grid[row][col].color = gx.black
grid[row][col].flag = 3
}
'start' {
grid[row][col].color = gx.orange
grid[row][col].flag = 4
}
'end' {
grid[row][col].color = gx.blue
grid[row][col].flag = 5
}
'path' {
grid[row][col].color = gx.pink
grid[row][col].flag = 6
}
else {}
}
}
// ------------------------------ HEAP -----------------------------
fn (mut heap MinHeap) insert(item Node) {
heap.data << item
}
// get the minimum out of all node
fn (mut heap MinHeap) pop() !Node {
if heap.len() == 0 {
return error('empty heap')
}
mut i := 0
mut curr := heap.data[0].f_score
len := heap.len()
for idx := 0; idx < len; idx++ {
if curr > heap.data[idx].f_score {
i = idx
curr = heap.data[idx].f_score
}
}
ele := heap.data[i]
heap.data.delete(i)
return ele
}
// see the top element of heap //TODO this won't give correct result as heap is not implemented correctly
fn (mut heap MinHeap) peek() !Node {
if heap.data.len == 0 {
return error('Heap is empty')
}
return heap.data[0]
}
// give length of heap total element present currently
fn (mut heap MinHeap) len() int {
return heap.data.len
}
// Index of left child of a node in heap //TODO heap not implemented
fn (mut heap MinHeap) left_child(idx int) !int {
child := 2 * idx + 1
if child >= heap.data.len {
return error('Out of Bound')
}
return child
}
// Index of right child of a node in heap //TODO heap not implemented
fn (mut heap MinHeap) right_child(idx int) !int {
child := 2 * idx + 2
if child >= heap.data.len {
return error('Out of bound')
}
return child
}
// Index of parent of a node in heap //TODO heap not implemented
fn (mut heap MinHeap) parent(idx int) int {
return (idx - 1) / 2
}
// comaparator of heap //TODO not used
fn comparator(n1 Node, n2 Node) bool {
if n1.f_score == n2.f_score {
return n1.count > n2.count
}
return n1.f_score > n2.f_score
}