mirror of
https://github.com/vlang/v.git
synced 2025-08-04 02:07:28 -04:00
480 lines
11 KiB
V
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
|
|
}
|