diff --git a/include/astar.h b/include/astar.h index f96e2e6..850f525 100644 --- a/include/astar.h +++ b/include/astar.h @@ -25,9 +25,11 @@ typedef struct AField { afield_t generate_astar_field(int rows, int columns, int** col_map); -void get_a_star_path(afield_t* field, Vector2 start, Vector2 goal); +Vector2* get_a_star_path(afield_t *field, Vector2 start, Vector2 goal, int max_searches, int* path_length); -void setup_start_field(afield_t* field, Vector2 start, Vector2 target); +Vector2* do_full_a_star_search(int width, int height, int** collisions, Vector2 start, Vector2 target, int max_searches, int* path_len); + +void setup_start_cell(afield_t* field, Vector2 start, Vector2 target); Vector2 get_best_node(afield_t *field); void collapse_node_at(afield_t *field, Vector2 node_pos); @@ -35,6 +37,6 @@ int assign_node_as_parent(afield_t *field, Vector2 target, Vector2 node_pos); void debug_print_field_collisions(afield_t* field); -void debug_print_field(afield_t* field, bool show_parent); +void debug_print_field(afield_t* field); int adjusted_euclidean(Vector2 a, Vector2 b); diff --git a/include/visual.h b/include/visual.h new file mode 100644 index 0000000..4d0bd6d --- /dev/null +++ b/include/visual.h @@ -0,0 +1,2 @@ + +int run_visual(void); diff --git a/src/astar.c b/src/astar.c index 944b7cb..6232169 100644 --- a/src/astar.c +++ b/src/astar.c @@ -41,6 +41,17 @@ afield_t generate_astar_field(int rows, int columns, int** col_map) { return field; } +void free_field(afield_t* field) { + if (!field || !field->map) + return; + + for (int i = 0; i < field->rows; i++) { + free(field->map[i]); + } + free(field->map); + field->map = NULL; +} + int adjusted_euclidean(Vector2 a, Vector2 b) { int dx = b.x - a.x; int dy = b.y - a.y; @@ -182,40 +193,91 @@ Vector2 get_best_node(afield_t *field) { return best_pos; } -void get_a_star_path(afield_t *field, Vector2 start, Vector2 goal) { - // TODO: check if start or end node is obstacle +Vector2* reconstruct_path(afield_t* field, Vector2 start, Vector2 end, int *out_length) { + // entirely chatgpt i had no motivation left + int rows = field->rows; + int columns = field->columns; + anode_t** map = field->map; - if (start.x == goal.x && start.y == goal.y) { - // no path required - return; // TODO: Return valid empty path - } + int max_len = rows * columns; // safe upper bound + Vector2 *path = malloc(max_len * sizeof(Vector2)); + if (!path) return NULL; - // setup start node - anode_t *start_node_p = &field->map[(int)start.y][(int)start.x]; - start_node_p->g = 0; - int heuristic = octile_heuristic(start, goal); - start_node_p->h = heuristic; - start_node_p->f = heuristic; - start_node_p->is_collapsed = true; + int count = 0; + Vector2 current = end; - // set values of surrounding nodes - assign_node_as_parent(field, goal, start); + while (!(current.x == start.x && current.y == start.y)) { + path[count++] = current; - bool found_target = false; - while (!found_target) { - // get node with best f score - Vector2 best = get_best_node(field); - if (best.x == -1 && best.y == -1) { - printf("Target can not be reached!!"); - } - // collase node with best f score and recalculate children - collapse_node_at(field, best); - assign_node_as_parent(field, goal, best); - // repeat until child is target - } + int px = (int)current.x; + int py = (int)current.y; + + // bounds check + if (px < 0 || px >= columns || py < 0 || py >= rows) break; + + current = map[py][px].parent; + + if (count >= max_len) break; // prevent infinite loops + } + + path[count++] = start; + + // reverse path so it goes start -> end + for (int i = 0; i < count / 2; i++) { + Vector2 tmp = path[i]; + path[i] = path[count - 1 - i]; + path[count - 1 - i] = tmp; + } + + *out_length = count; + return path; } -void setup_start_field(afield_t* field, Vector2 start, Vector2 target) { +Vector2* get_a_star_path(afield_t *field, Vector2 start, Vector2 goal, int max_searches, int* path_length) { + setup_start_cell(field, start, goal); + collapse_node_at(field, start); + + assign_node_as_parent(field, goal, start); + debug_print_field(field); + + for (int x = 0; x < max_searches; x++) { + Vector2 best = get_best_node(field); + if (best.x == -1 || best.y == -1) { + // all search paths exausted + debug_print_field(field); + printf("TARGET NOT REACHABLE!\n"); + + *path_length = 0; + return NULL; + } + // printf("Best node: %d %d\n", (int)best.x, (int)best.y); + + int found = assign_node_as_parent(field, goal, best); + collapse_node_at(field, best); + // printf("collapsed node at: %d %d", (int)best.x, (int)best.y); + + if (found) { + // debug_print_field(field); + printf("FOUND TARGET!\n"); + return reconstruct_path(field, start, goal, path_length); + } + } + + printf("All searches wasted, target not found, quitting!\n"); + *path_length = 0; + return NULL; +} + +Vector2* do_full_a_star_search(int width, int height, int** collisions, Vector2 start, Vector2 target, int max_searches, int* path_len) { + afield_t field = generate_astar_field(height, width, collisions); + + Vector2* path = get_a_star_path(&field, start, target, max_searches, path_len); + + free_field(&field); + return path; +} + +void setup_start_cell(afield_t* field, Vector2 start, Vector2 target) { int heuristic = octile_heuristic(start, target); field->map[(int)start.y][(int)start.x].g = 0; @@ -234,7 +296,7 @@ void debug_print_field_collisions(afield_t* field) { printf("\n"); } -void debug_print_field(afield_t* field, bool show_parent) { +void debug_print_field(afield_t* field) { char* arrows[3][3] = { {"↖", "↑", "↗"}, {"←", "•", "→"}, @@ -252,18 +314,13 @@ void debug_print_field(afield_t* field, bool show_parent) { // printf("[]"); // } else { - if (show_parent) { - Vector2 parent = field->map[r][c].parent; - if (parent.x != -1 && field->map[r][c].is_collapsed) { - Vector2 par_offs = Vector2Subtract(parent, (Vector2) {c, r}); - printf("%s", arrows[(int)par_offs.y+1][(int)par_offs.x+1]); - } - else { - printf(" "); - } + Vector2 parent = field->map[r][c].parent; + if (parent.x != -1 && field->map[r][c].is_collapsed) { + Vector2 par_offs = Vector2Subtract(parent, (Vector2) {c, r}); + printf("%s", arrows[(int)par_offs.y+1][(int)par_offs.x+1]); } else { - printf("%2i", field->map[r][c].f); + printf(" "); } } printf("| "); diff --git a/src/main.c b/src/main.c index ee9c545..534c9ab 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include #include "../include/astar.h" +#include "../include/visual.h" int** create_array(int x, int y) { int rows = y, cols = x; @@ -12,22 +13,9 @@ int** create_array(int x, int y) { myArray[i] = malloc(cols * sizeof(int)); } - int values[10][10] = { - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1} - }; - for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { - myArray[i][j] = values[i][j]; + myArray[i][j] = -1; } } @@ -35,36 +23,20 @@ int** create_array(int x, int y) { } int main() { - int** dyn_array = create_array(10, 10); - afield_t field = generate_astar_field(10, 10, dyn_array); + run_visual(); - Vector2 start = { 0, 0 }; - Vector2 goal = { 9, 0 }; - - setup_start_field(&field, start, goal); - collapse_node_at(&field, start); - - assign_node_as_parent(&field, goal, start); - debug_print_field(&field, true); - - for (int x = 0; x < 100; x++) { - Vector2 best = get_best_node(&field); - if (best.x == -1 || best.y == -1) { - // all search paths exausted - debug_print_field(&field, true); - printf("TARGET NOT REACHABLE!\n"); - return 0; - } - // printf("Best node: %d %d\n", (int)best.x, (int)best.y); + // int** collision = create_array(20, 20); + // afield_t field = generate_astar_field(20, 20, collision); - int found = assign_node_as_parent(&field, goal, best); - collapse_node_at(&field, best); - // printf("collapsed node at: %d %d", (int)best.x, (int)best.y); + // Vector2 start = { 0, 0 }; + // Vector2 goal = { 19, 19 }; + + // int path_len = -1; + // Vector2* path = do_full_a_star_search(20, 20, collision, start, goal, 100, &path_len); + // if (!path) { printf("no path"); return 0; } - if (found) { - debug_print_field(&field, true); - printf("FOUND TARGET!\n"); - return 0; - } - } + // for (int x = 0; x < path_len; x++) { + // printf("node at [%d|%d]\n", (int)path[x].x, (int)path[x].y); + // } + // free(path); } diff --git a/src/visual.c b/src/visual.c new file mode 100644 index 0000000..a43c79e --- /dev/null +++ b/src/visual.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include + +#include "../include/astar.h" + +#define GRID_SIZE 20 + +int run_visual(void) { + const int screenWidth = 800; + const int screenHeight = 800; + + const int cellSize = screenWidth / GRID_SIZE; + + int path_len = -1; + Vector2* path = NULL; + + InitWindow(screenWidth, screenHeight, "A*"); + + bool grid[GRID_SIZE][GRID_SIZE] = {0}; + + SetTargetFPS(60); + + while (!WindowShouldClose()) { + // Handle mouse clicks + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { + Vector2 mousePos = GetMousePosition(); + int x = mousePos.x / cellSize; + int y = mousePos.y / cellSize; + + if (x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE) { + grid[y][x] = true; + } + } + if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) { + Vector2 mousePos = GetMousePosition(); + int x = mousePos.x / cellSize; + int y = mousePos.y / cellSize; + + if (x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE) { + grid[y][x] = false; + } + } + if (IsKeyDown(KEY_SPACE)) { + int **collision = malloc(GRID_SIZE * sizeof(int*)); + for(int i = 0; i < GRID_SIZE; i++) + collision[i] = malloc(GRID_SIZE * sizeof(int)); + + + for(int i = 0; i < GRID_SIZE; i++) { + for(int j = 0; j < GRID_SIZE; j++) { + collision[i][j] = grid[i][j] ? 8 : -1; + } + } + + afield_t field = generate_astar_field(GRID_SIZE, GRID_SIZE, collision); + + Vector2 start = { 0, 0 }; + Vector2 goal = { GRID_SIZE-1, GRID_SIZE-1 }; + + if (path) { free(path); } + path = do_full_a_star_search(GRID_SIZE, GRID_SIZE, collision, start, goal, 1000, &path_len); + + if (!path) { printf("no path"); } + } + + BeginDrawing(); + ClearBackground(RAYWHITE); + + // Draw grid + for (int y = 0; y < GRID_SIZE; y++) { + for (int x = 0; x < GRID_SIZE; x++) { + bool cell_in_path = false; + if (path) { + for (int i = 0; i < path_len; i++) { + if (path[i].x == x && path[i].y == y) { + cell_in_path = true; + } + } + } + + Rectangle cell = { x * cellSize, y * cellSize, cellSize, cellSize }; + + if (cell_in_path) { + DrawRectangleRec(cell, RED); + } + + if (grid[y][x]) { + DrawRectangleRec(cell, DARKBLUE); + } + + DrawRectangleLines(cell.x, cell.y, cell.width, cell.height, GRAY); + } + } + + EndDrawing(); + } + + CloseWindow(); + return 0; +} + diff --git a/target b/target index 24823b9..0ed9c46 100755 Binary files a/target and b/target differ