pathfinding with astar
This commit is contained in:
+1
-1
@@ -82,7 +82,7 @@ entity_set_move_target :: proc(
|
||||
}
|
||||
|
||||
start := ew.position[i]
|
||||
if !pathfind_bfs(tm, start, target, &ew.path[i]) {
|
||||
if !pathfind_astar(tm, start, target, &ew.path[i]) {
|
||||
return false
|
||||
}
|
||||
ew.path_index[i] = 1
|
||||
|
||||
@@ -5,6 +5,8 @@ Path :: struct {
|
||||
count: int,
|
||||
}
|
||||
|
||||
max_path_int :: 1_000_000_000
|
||||
|
||||
path_clear :: proc(path: ^Path) {
|
||||
delete(path.tiles)
|
||||
path.tiles = nil
|
||||
@@ -15,6 +17,7 @@ pathfind_bfs :: proc(tilemap: ^Tilemap, start, goal: Tile_Coord, path: ^Path) ->
|
||||
path_clear(path)
|
||||
|
||||
if start == goal {
|
||||
path.tiles = make([]Tile_Coord, 1) // allocate because path_clear set tiles to nil
|
||||
path.tiles[0] = start
|
||||
path.count = 1
|
||||
return true
|
||||
@@ -91,3 +94,123 @@ pathfind_bfs :: proc(tilemap: ^Tilemap, start, goal: Tile_Coord, path: ^Path) ->
|
||||
return true
|
||||
}
|
||||
|
||||
heuristic :: proc(a, b: Tile_Coord) -> int {
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
}
|
||||
|
||||
pathfind_astar :: proc(tm: ^Tilemap, start, goal: Tile_Coord, path: ^Path) -> bool {
|
||||
path_clear(path)
|
||||
|
||||
if start == goal {
|
||||
path.tiles = make([]Tile_Coord, 1)
|
||||
path.tiles[0] = start
|
||||
path.count = 1
|
||||
return true
|
||||
}
|
||||
|
||||
if !tile_walkable(tm, goal.x, goal.y) {
|
||||
return false
|
||||
}
|
||||
|
||||
w, h := tm.width, tm.height
|
||||
|
||||
g_score: [MAP_HEIGHT][MAP_WIDTH]int
|
||||
f_score: [MAP_HEIGHT][MAP_WIDTH]int
|
||||
parents: [MAP_HEIGHT][MAP_WIDTH]Tile_Coord
|
||||
in_open: [MAP_HEIGHT][MAP_WIDTH]bool
|
||||
closed: [MAP_HEIGHT][MAP_WIDTH]bool
|
||||
|
||||
for y in 0 ..< h {
|
||||
for x in 0 ..< w {
|
||||
g_score[y][x] = max_path_int
|
||||
f_score[y][x] = max_path_int
|
||||
parents[y][x] = {-1, -1}
|
||||
}
|
||||
}
|
||||
|
||||
g_score[start.y][start.x] = 0
|
||||
f_score[start.y][start.x] = heuristic(start, goal)
|
||||
parents[start.y][start.x] = start
|
||||
in_open[start.y][start.x] = true
|
||||
|
||||
open_list: [MAP_WIDTH * MAP_HEIGHT]Tile_Coord
|
||||
open_count := 1
|
||||
open_list[0] = start
|
||||
|
||||
found := false
|
||||
|
||||
for open_count > 0 {
|
||||
best_i := 0
|
||||
best_f := max_path_int
|
||||
for i in 0 ..< open_count {
|
||||
c := open_list[i]
|
||||
if f_score[c.y][c.x] < best_f {
|
||||
best_f = f_score[c.y][c.x]
|
||||
best_i = i
|
||||
}
|
||||
}
|
||||
|
||||
current := open_list[best_i]
|
||||
open_list[best_i] = open_list[open_count - 1]
|
||||
open_count -= 1
|
||||
in_open[current.y][current.x] = false
|
||||
closed[current.y][current.x] = true
|
||||
|
||||
if current == goal {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
for offset in NEIGHBOR_OFFSET {
|
||||
nx := current.x + offset.x
|
||||
ny := current.y + offset.y
|
||||
if !tile_in_bounds(tm, nx, ny) || closed[ny][nx] {
|
||||
continue
|
||||
}
|
||||
|
||||
step_cost := tile_cost(tm, nx, ny)
|
||||
if step_cost >= max_path_int {
|
||||
continue
|
||||
}
|
||||
|
||||
tentative := g_score[current.y][current.x] + step_cost
|
||||
if tentative < g_score[ny][nx] {
|
||||
parents[ny][nx] = current
|
||||
g_score[ny][nx] = tentative
|
||||
f_score[ny][nx] = tentative + heuristic({nx, ny}, goal)
|
||||
|
||||
if !in_open[ny][nx] {
|
||||
in_open[ny][nx] = true
|
||||
open_list[open_count] = {nx, ny}
|
||||
open_count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
rev: [MAX_PATH]Tile_Coord
|
||||
rev_count := 0
|
||||
cur := goal
|
||||
for {
|
||||
rev[rev_count] = cur
|
||||
rev_count += 1
|
||||
if cur == start {
|
||||
break
|
||||
}
|
||||
cur = parents[cur.y][cur.x] // walk backward through the parent chain toward start
|
||||
if rev_count >= MAX_PATH {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
path.tiles = make([]Tile_Coord, rev_count) // allocate output slice since path_clear left it nil
|
||||
path.count = rev_count
|
||||
for i in 0 ..< rev_count {
|
||||
path.tiles[i] = rev[rev_count - 1 - i] // reverse the collected tiles so order is start -> goal
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+33
-2
@@ -15,8 +15,17 @@ tilemap_init :: proc() -> Tilemap {
|
||||
tiles = make([]u8, MAP_WIDTH * MAP_HEIGHT),
|
||||
}
|
||||
// test wall
|
||||
for x in 15 ..< 25 {
|
||||
tm.tiles[tile_index(&tm, x, 12)] = 1
|
||||
// Horizontal mud strip (expensive, cost 20 per step)
|
||||
for y in 8 ..< 18 {
|
||||
tm.tiles[tile_index(&tm, 5, y)] = 3 // mud
|
||||
}
|
||||
// Road detour (cheap, cost 1 per step) — goes right, down, left
|
||||
for x in 5 ..< 12 {
|
||||
tm.tiles[tile_index(&tm, x, 8)] = 2 // road top
|
||||
tm.tiles[tile_index(&tm, x, 18)] = 2 // road bottom
|
||||
}
|
||||
for y in 8 ..< 19 {
|
||||
tm.tiles[tile_index(&tm, 11, y)] = 2 // road right side
|
||||
}
|
||||
return tm
|
||||
}
|
||||
@@ -39,6 +48,12 @@ tile_color :: proc(type_id: u8) -> rl.Color {
|
||||
switch type_id {
|
||||
case 0:
|
||||
return {72, 112, 68, 255}
|
||||
case 1:
|
||||
return {90, 90, 95, 255} // wall — gray
|
||||
case 2:
|
||||
return {160, 140, 100, 255} // road — tan/brown
|
||||
case 3:
|
||||
return {120, 100, 70, 255} // mud — darker brown
|
||||
case:
|
||||
return rl.MAGENTA
|
||||
}
|
||||
@@ -87,3 +102,19 @@ tile_walkable :: proc(tilemap: ^Tilemap, x, y: int) -> bool {
|
||||
return tilemap.tiles[idx] != 1
|
||||
}
|
||||
|
||||
tile_cost :: proc(tm: ^Tilemap, x, y: int) -> int {
|
||||
if !tile_walkable(tm, x, y) {
|
||||
return max_path_int
|
||||
}
|
||||
switch tm.tiles[tile_index(tm, x, y)] {
|
||||
case 0:
|
||||
return 10
|
||||
case 2:
|
||||
return 1
|
||||
case 3:
|
||||
return 20
|
||||
case:
|
||||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user