CurrentMap: Map; Tile :: typedef int; TILE_BLOCKER :: 1; TILE_ACTOR_IS_STANDING :: <<; Map :: struct { data: *Tile; x: int; y: int; actors: ArrayOfActors; } V2I :: struct { x: int; y: int; } Actor :: struct { p: V2I; target_p: V2I; map: *Map; open_paths: ArrayOfPaths; close_paths: ArrayOfPaths; tiles_visited: ArrayOfV2Is; history: ArrayOfPaths; } Path :: struct { value_to_sort_by: int; p: V2I; came_from: V2I; } ArrayOfActors :: struct { data: *Actor; len: int; cap: int; } ArrayOfPaths :: struct { data: *Path; len: int; cap: int; } ArrayOfV2Is :: struct { data: *V2I; len: int; cap: int; } Rect :: proc(p: V2I): Rectangle { result: Rectangle = {:float(p.x) * RectX, :float(p.y) * RectY, RectX, RectY}; return result; } Circle :: proc(p: V2I): Vector2 { result: Vector2 = {:float(p.x) * RectX + RectX/2, :float(p.y) * RectY + RectY/2}; return result; } ScreenToMap :: proc(p: Vector2): V2I { x := p.x / RectX; y := p.y / RectY; result: V2I = {:int(x), :int(y)}; return result; } MaxInt :: proc(x: int, y: int): int { if x > y return x; return y; } Assert :: proc(x: bool) { if (!x) { libc.printf("assertion failed\n"); libc.fflush(libc.stdout); libc.debug_break(); } } InvalidCodepath :: proc() { libc.printf("invalid codepath\n"); libc.fflush(libc.stdout); libc.debug_break(); } AddActor :: proc(map: *Map, p: V2I): *Actor { AddActorToArray(&map.actors, {p, p, map}); map.data[p.x + p.y * map.x] |= TILE_ACTOR_IS_STANDING; result := GetLastActor(map.actors); return result; } SetActorP :: proc(actor: *Actor, p: V2I) { map := actor.map; new_tile := &map.data[p.x + p.y * map.x]; if *new_tile != 0 return; tile := &map.data[actor.p.x + actor.p.y * map.x]; Assert((*tile & TILE_ACTOR_IS_STANDING) != 0); *tile &= ~TILE_ACTOR_IS_STANDING; *new_tile |= TILE_ACTOR_IS_STANDING; actor.p = p; actor.tiles_visited.len = 0; actor.history.len = 0; actor.open_paths.len = 0; actor.close_paths.len = 0; } SetTargetP :: proc(actor: *Actor, p: V2I) { actor.target_p = p; actor.tiles_visited.len = 0; actor.history.len = 0; actor.open_paths.len = 0; actor.close_paths.len = 0; } GetRandomP :: proc(m: *Map): V2I { result: V2I = {GetRandomInt(0, m.x - 1), GetRandomInt(0, m.y - 1)}; return result; } GetRandomUnblockedP :: proc(m: *Map): V2I { for i := 0; i < 128; i += 1 { p := GetRandomP(m); if m.data[p.x + p.y * m.x] == 0 { return p; } } Assert(!:ullong(:*char("invalid codepath"))); return {}; } InitMap :: proc() { CurrentMap.x = WinX / RectX; CurrentMap.y = WinY / RectY; bytes := sizeof(:Tile) * CurrentMap.x * CurrentMap.y; CurrentMap.data = libc.malloc(:libc.size_t(bytes)); libc.memset(CurrentMap.data, 0, :libc.size_t(bytes)); actor := AddActor(&CurrentMap, GetRandomUnblockedP(&CurrentMap)); actor.target_p = GetRandomUnblockedP(&CurrentMap); actor2 := AddActor(&CurrentMap, GetRandomUnblockedP(&CurrentMap)); actor2.target_p = GetRandomUnblockedP(&CurrentMap); } RandomizeActors :: proc() { map := &CurrentMap; for i := 0; i < map.actors.len; i += 1 { it := &map.actors.data[i]; p := GetRandomUnblockedP(&CurrentMap); SetActorP(it, p); it.target_p = GetRandomUnblockedP(&CurrentMap); } } InsertOpenPath :: proc(actor: *Actor, p: V2I, came_from: V2I, ignore_blocks: bool = false) { if p.x < 0 || p.x >= actor.map.x return; if p.y < 0 || p.y >= actor.map.y return; if ignore_blocks == false && actor.map.data[p.x + p.y * actor.map.x] != 0 return; for i := 0; i < actor.close_paths.len; i += 1 { it := &actor.close_paths.data[i]; if it.p.x == p.x && it.p.y == p.y return; } for i := 0; i < actor.open_paths.len; i += 1 { it := &actor.open_paths.data[i]; if it.p.x == p.x && it.p.y == p.y return; } dx := actor.target_p.x - p.x; dy := actor.target_p.y - p.y; d := dx*dx + dy*dy; InsertSortedPath(&actor.open_paths, {d, p, came_from}); } GetCloseP :: proc(actor: *Actor, p: V2I): *Path { for i := 0; i < actor.close_paths.len; i += 1 { it := &actor.close_paths.data[i]; if it.p.x == p.x && it.p.y == p.y return it; } InvalidCodepath(); return nil; } RecomputeHistory :: proc(actor: *Actor) { if actor.close_paths.len > 1 { actor.history.len = 0; it := GetLastPath(actor.close_paths); AddPath(&actor.history, *it); for i := 0;;i += 1 { if it.p.x == actor.p.x && it.p.y == actor.p.y { break; } if i > 512 { actor.history.len = 0; break; } // @todo: Pop after this and error? it = GetCloseP(actor, it.came_from); AddPath(&actor.history, *it); } PopPath(&actor.history); } } MoveTowardsTarget :: proc(actor: *Actor) { tile := &actor.map.data[actor.p.x + actor.p.y * actor.map.x]; if actor.history.len > 0 { step := PopPath(&actor.history); new_tile := &actor.map.data[step.p.x + step.p.y * actor.map.x]; if *new_tile == 0 { AddV2I(&actor.tiles_visited, actor.p); actor.p = step.p; *tile &= ~TILE_ACTOR_IS_STANDING; *new_tile |= TILE_ACTOR_IS_STANDING; } } } PathFindUpdate :: proc(map: *Map) { for actor_i := 0; actor_i < map.actors.len; actor_i += 1 { actor_it := &map.actors.data[actor_i]; for i := 0; i < actor_it.history.len; i += 1 { history_it := &actor_it.history.data[i]; tile := actor_it.map.data[history_it.p.x + history_it.p.y * actor_it.map.x]; if tile != 0 { actor_it.close_paths.len = 0; actor_it.open_paths.len = 0; actor_it.history.len = 0; break; } } PathFind(actor_it); } } PathFindStep :: proc(s: *Actor, compute_history: bool = true): bool { if s.open_paths.len == 0 { // Reset if we didn't find solution if s.close_paths.len != 0 { last := GetLastPath(s.close_paths); reached_target := last.p.x == s.target_p.x && last.p.y == s.target_p.y; if reached_target == false { s.close_paths.len = 0; s.open_paths.len = 0; s.history.len = 0; } } InsertOpenPath(s, s.p, s.p, ignore_blocks = true); } if s.close_paths.len != 0 { last := GetLastPath(s.close_paths); reached_target := last.p.x == s.target_p.x && last.p.y == s.target_p.y; if reached_target return true; } it := PopPath(&s.open_paths); AddPath(&s.close_paths, it); for y := -1; y <= 1; y += 1 { for x := -1; x <= 1; x += 1 { if x == 0 && y == 0 continue; p: V2I = {it.p.x + x, it.p.y + y}; InsertOpenPath(s, p, it.p); } } if compute_history RecomputeHistory(s); return false; } PathFind :: proc(actor: *Actor) { for i := 0; i < 32; i += 1 { done := PathFindStep(actor, false); if done break; } RecomputeHistory(actor); }