v2 / examples / gg / path_finding_algorithm_visualizer / aStar.v
455 lines · 417 sloc · 10.77 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1module main
2
3import gg // actual graphics lib
4import math // for math related function
5
6const window_width = 800
7const window_height = 800
8const nrows = 50
9
10// app struct that has property of current windows
11struct App {
12mut:
13 gg &gg.Context = unsafe { nil }
14 ui Ui
15 grid [][]Cell
16 start Point // start point of algorithm
17 end Point // end point or target point
18}
19
20// this needed to get the width and mouse position part of gg window
21struct Ui {
22mut:
23 dpi_scale f32
24}
25
26// struct for a point
27struct Point {
28mut:
29 x int
30 y int
31}
32
33/*
34RED -> Closed
35GREEN -> Open
36BLACK -> Barrier
37WHITE -> Empty
38ORANGE -> Start
39TURQOIISE -> End
40PINK -> Path
41*/
42
43// struct for a cell of grid
44struct Cell {
45mut:
46 row int
47 col int
48 width int
49 pos Point
50 color gg.Color
51 flag int // 0->empty, 1-> closed, 2-> open, 3-> barrier, 4-> start, 5-> end, 6-> path
52 neighbors []Point
53}
54
55// this is a node for priority queue
56
57struct Node {
58mut:
59 f_score int
60 cell Point
61 count int
62}
63
64// Min heap or priority queue
65
66struct MinHeap {
67mut:
68 data []Node
69}
70
71// main function
72fn main() {
73 // app variable
74 mut app := &App{}
75
76 // setting values of app
77 app.gg = gg.new_context(
78 bg_color: gg.black // background color
79 width: window_width // window width
80 height: window_height // window height
81 create_window: true // this will create a different window
82 window_title: 'A* Path finding algorithm visusalizer' // title of the window
83 frame_fn: frame // this is frame function update the frame
84 event_fn: on_event // it calls on every event
85 user_data: app // store user data
86 )
87 mut grid :=
88 initialise_grid() // initialize the grid variable and populate the matrix with each cell as empty
89 app.grid = grid // set grid to app attribute so you can access it by just passing app variable or with method of app
90 app.ui.dpi_scale = 1.0 // set scale this is use to make it responsive
91 app.start = &Point{ // set start point to -1, -1
92 x: -1
93 y: -1
94 }
95 app.end = &Point{ // set end point to -1, -1
96 x: -1
97 y: -1
98 }
99 app.gg.run() // run the app loop
100}
101
102// this function will run for every frame actually in a loop
103fn frame(mut app App) {
104 app.gg.begin()
105 draw_grid(mut app)
106 draw_gridlines(mut app)
107 app.gg.end()
108}
109
110// this will handle user event which is stored in gg.event variable
111fn on_event(event &gg.Event, mut app App) {
112 match event.typ {
113 .mouse_down {
114 x := int(event.mouse_x / app.ui.dpi_scale)
115 y := int(event.mouse_y / app.ui.dpi_scale)
116 btn := event.mouse_button
117 app.handle_mouse_event(x, y, btn)
118 }
119 .key_down {
120 app.on_key_down(event.key_code)
121 }
122 else {}
123 }
124}
125
126// handle mouse event to make a cell either start point end point or barrier or to clear
127fn (mut app App) handle_mouse_event(x int, y int, btn_type gg.MouseButton) {
128 gap := window_width / nrows
129 row := int(y / gap)
130 col := int(x / gap)
131 match btn_type {
132 .left {
133 if app.start.x == -1 && !(row == app.end.y && col == app.end.x) {
134 app.start.x = col
135 app.start.y = row
136 set_cell_type(mut app.grid, app.start.y, app.start.x, 'start')
137 } else if app.end.x == -1 && !(row == app.start.y && col == app.start.x) {
138 app.end.x = col
139 app.end.y = row
140 set_cell_type(mut app.grid, app.end.y, app.end.x, 'end')
141 } else if !(row == app.start.y && col == app.start.x) && !(row == app.end.y
142 && col == app.end.x) {
143 set_cell_type(mut app.grid, row, col, 'barrier')
144 }
145 }
146 .right {
147 if row == app.start.y && col == app.start.x {
148 app.start.x = -1
149 app.start.y = -1
150 }
151 if row == app.end.y && col == app.end.x {
152 app.end.x = -1
153 app.end.y = -1
154 }
155
156 set_cell_type(mut app.grid, row, col, 'reset')
157 }
158 else {}
159 }
160}
161
162// handle keyboard interaction by user ''
163fn (mut app App) on_key_down(key gg.KeyCode) {
164 match key {
165 .space {
166 if app.start.x == -1 || app.end.x == -1 {
167 println('Error: either start or end node is missing')
168 } else {
169 for row := 0; row < nrows; row++ {
170 for j := 0; j < nrows; j++ {
171 update_neighbors(mut app.grid, row, j)
172 }
173 }
174 new_start := &Point{
175 x: app.start.y
176 y: app.start.x
177 }
178 new_end := &Point{
179 x: app.end.y
180 y: app.end.x
181 }
182 astar_path_finding(mut app, mut app.grid, new_start, new_end)
183 }
184 }
185 .q {
186 app.gg.quit()
187 }
188 .c {
189 draw_grid(mut app)
190 draw_gridlines(mut app)
191 mut grid := initialise_grid()
192 app.grid = grid // set grid to app attribute so you can access it by just passing app variable or with method of app
193 app.ui.dpi_scale = 1.0 // set scale this is use to make it responsive
194 app.start = &Point{ // set start point to -1, -1
195 x: -1
196 y: -1
197 }
198 app.end = &Point{ // set end point to -1, -1
199 x: -1
200 y: -1
201 }
202 }
203 else {}
204 }
205}
206
207// draw grid lines
208fn draw_gridlines(mut app App) {
209 dx := window_width / nrows
210 dy := window_height / nrows
211 for i := 0; i < nrows; i++ {
212 // horizontal lines
213 app.gg.draw_line(0, i * dy, window_width, i * dy, gg.black)
214 // vertical lines
215 app.gg.draw_line(i * dx, 0, dx * i, window_height, gg.black)
216 }
217}
218
219// heuristic function(point manhatten distance) that calculate approximate cost to reach from a given point to end(target)
220fn hf(p1 Point, p2 Point) int {
221 return math.abs(p1.x - p2.x) + math.abs(p1.y - p2.y)
222}
223
224// initialize grid attribute of app
225fn initialise_grid() [][]Cell {
226 mut grid := [][]Cell{len: nrows, init: []Cell{len: nrows}}
227 gap := window_width / nrows
228 for i := 0; i < nrows; i++ {
229 for j := 0; j < nrows; j++ {
230 grid[i][j] = &Cell{
231 row: i
232 col: j
233 width: gap
234 pos: &Point{
235 x: j * gap
236 y: i * gap
237 }
238 color: gg.white
239 flag: 0
240 }
241 }
242 }
243 return grid
244}
245
246// draw the cells of grid
247fn draw_grid(mut app App) {
248 for i := 0; i < nrows; i++ {
249 for j := 0; j < nrows; j++ {
250 pos := app.grid[i][j].pos
251 width := app.grid[i][j].width
252 color := app.grid[i][j].color
253 app.gg.draw_rect_filled(pos.x, pos.y, width, width, color)
254 }
255 }
256}
257
258// update the neighbor of each cell in which cell you can visit (if it is not barrier or end or start)
259fn update_neighbors(mut grid [][]Cell, row int, col int) {
260 if row < nrows - 1 && grid[row + 1][col].flag != 3 {
261 grid[row][col].neighbors << &Point{
262 x: row + 1
263 y: col
264 }
265 }
266 if row > 0 && grid[row - 1][col].flag != 3 {
267 grid[row][col].neighbors << &Point{
268 x: row - 1
269 y: col
270 }
271 }
272 if col < nrows - 1 && grid[row][col + 1].flag != 3 {
273 grid[row][col].neighbors << &Point{
274 x: row
275 y: col + 1
276 }
277 }
278 if col > 0 && grid[row][col - 1].flag != 3 {
279 grid[row][col].neighbors << &Point{
280 x: row
281 y: col - 1
282 }
283 }
284}
285
286// construct the path after finding it shows as pink color
287fn reconstruct_path(mut grid [][]Cell, mut came_from [][]Point, _start Point, end Point) {
288 mut x := end.x
289 mut y := end.y
290 for !(x == -1 && y == -1) {
291 set_cell_type(mut grid, x, y, 'path')
292 x = came_from[x][y].x
293 y = came_from[x][y].y
294 }
295}
296
297// a* path finding algorithm
298fn astar_path_finding(mut _app App, mut grid [][]Cell, start Point, end Point) {
299 mut priority_queue := &MinHeap{}
300 mut g_score := [][]int{len: nrows, init: []int{len: nrows}}
301 mut f_score := [][]int{len: nrows, init: []int{len: nrows}}
302 mut came_from := [][]Point{len: nrows, init: []Point{len: nrows}}
303 for i := 0; i < nrows; i++ {
304 for j := 0; j < nrows; j++ {
305 g_score[i][j] = 1_000_000_000_00
306 f_score[i][j] = 1_000_000_000_00
307 came_from[i][j] = &Point{
308 x: -1
309 y: -1
310 }
311 }
312 }
313
314 g_score[start.x][start.y] = 0
315 f_score[start.x][start.y] = g_score[start.x][start.y] + hf(start, end)
316 priority_queue.insert(Node{
317 f_score: f_score[start.x][start.y]
318 cell: &Point{
319 x: start.x
320 y: start.y
321 }
322 count: 0
323 })
324
325 for priority_queue.len() > 0 {
326 curr_node := priority_queue.pop() or {
327 panic('There is nothing in queue how did it reach here idk')
328 }
329 curr_pos := curr_node.cell
330 set_cell_type(mut grid, curr_pos.x, curr_pos.y, 'close')
331
332 if curr_pos.x == end.x && curr_pos.y == end.y {
333 set_cell_type(mut grid, start.x, start.y, 'start')
334 set_cell_type(mut grid, end.x, end.y, 'end')
335 came_from[end.x][end.y] = came_from[curr_pos.x][curr_pos.y]
336 reconstruct_path(mut grid, mut came_from, start, end)
337 set_cell_type(mut grid, start.x, start.y, 'start')
338 set_cell_type(mut grid, end.x, end.y, 'end')
339 return
340 }
341
342 for neighbor in grid[curr_pos.x][curr_pos.y].neighbors {
343 mut temp_g_score := g_score[curr_pos.x][curr_pos.y] + 1
344 if temp_g_score < g_score[neighbor.x][neighbor.y] {
345 g_score[neighbor.x][neighbor.y] = temp_g_score
346 if !(neighbor.x == start.x && neighbor.y == start.y) {
347 priority_queue.insert(Node{
348 f_score: g_score[neighbor.x][neighbor.y] + hf(neighbor, end)
349 cell: neighbor
350 count: curr_node.count + 1
351 })
352 came_from[neighbor.x][neighbor.y] = curr_pos
353 set_cell_type(mut grid, neighbor.x, neighbor.y, 'open')
354 }
355 }
356 }
357 }
358 set_cell_type(mut grid, start.x, start.y, 'start')
359}
360
361// change the property of a cell
362fn set_cell_type(mut grid [][]Cell, row int, col int, typ string) {
363 match typ {
364 'reset' {
365 grid[row][col].color = gg.white
366 grid[row][col].flag = 0
367 }
368 'close' {
369 grid[row][col].color = gg.red
370 grid[row][col].flag = 1
371 }
372 'open' {
373 grid[row][col].color = gg.green
374 grid[row][col].flag = 2
375 }
376 'barrier' {
377 grid[row][col].color = gg.black
378 grid[row][col].flag = 3
379 }
380 'start' {
381 grid[row][col].color = gg.orange
382 grid[row][col].flag = 4
383 }
384 'end' {
385 grid[row][col].color = gg.blue
386 grid[row][col].flag = 5
387 }
388 'path' {
389 grid[row][col].color = gg.pink
390 grid[row][col].flag = 6
391 }
392 else {}
393 }
394}
395
396// ------------------------------ HEAP -----------------------------
397
398fn (mut heap MinHeap) insert(item Node) {
399 heap.data << item
400}
401
402// get the minimum out of all node
403fn (mut heap MinHeap) pop() !Node {
404 if heap.len() == 0 {
405 return error('empty heap')
406 }
407 mut i := 0
408 mut curr := heap.data[0].f_score
409 len := heap.len()
410 for idx := 0; idx < len; idx++ {
411 if curr > heap.data[idx].f_score {
412 i = idx
413 curr = heap.data[idx].f_score
414 }
415 }
416 ele := heap.data[i]
417 heap.data.delete(i)
418 return ele
419}
420
421// see the top element of heap //TODO this won't give correct result as heap is not implemented correctly
422fn (mut heap MinHeap) peek() !Node {
423 if heap.data.len == 0 {
424 return error('Heap is empty')
425 }
426 return heap.data[0]
427}
428
429// give length of heap total element present currently
430fn (mut heap MinHeap) len() int {
431 return heap.data.len
432}
433
434// Index of left child of a node in heap //TODO heap not implemented
435fn (mut heap MinHeap) left_child(idx int) !int {
436 child := 2 * idx + 1
437 if child >= heap.data.len {
438 return error('Out of Bound')
439 }
440 return child
441}
442
443// Index of right child of a node in heap //TODO heap not implemented
444fn (mut heap MinHeap) right_child(idx int) !int {
445 child := 2 * idx + 2
446 if child >= heap.data.len {
447 return error('Out of bound')
448 }
449 return child
450}
451
452// Index of parent of a node in heap //TODO heap not implemented
453fn (mut heap MinHeap) parent(idx int) int {
454 return (idx - 1) / 2
455}
456