initial commit

This commit is contained in:
Magnus Küderli
2025-10-27 21:26:38 +01:00
commit 887e16328a
6 changed files with 425 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
SRC = src/*
TARGET = target
LIB = -lraylib -lm -lpthread -ldl -lrt -lX11
all: $(TARGET)
$(TARGET):
gcc $(SRC) -o $(TARGET) $(LIB)
clean:
rm -f $(TARGET)
run: $(TARGET)
gcc $(SRC) -o $(TARGET) $(LIB)
./$(TARGET)
+26
View File
@@ -0,0 +1,26 @@
[
{
"arguments": [
"/usr/bin/gcc",
"-c",
"-o",
"target",
"src/astar.c"
],
"directory": "/home/magnus/code/c/projects/astar",
"file": "/home/magnus/code/c/projects/astar/src/astar.c",
"output": "/home/magnus/code/c/projects/astar/target"
},
{
"arguments": [
"/usr/bin/gcc",
"-c",
"-o",
"target",
"src/main.c"
],
"directory": "/home/magnus/code/c/projects/astar",
"file": "/home/magnus/code/c/projects/astar/src/main.c",
"output": "/home/magnus/code/c/projects/astar/target"
}
]
+40
View File
@@ -0,0 +1,40 @@
# pragma once
# include <stdbool.h>
# include <raylib.h>
# include <raymath.h>
typedef struct ANode {
Vector2 parent;
int h;
int g;
int f;
bool is_collapsed;
bool is_obstacle;
} anode_t;
typedef struct AField {
int rows;
int columns;
anode_t** map;
} afield_t;
afield_t generate_astar_field(int rows, int columns, int** col_map);
void get_a_star_path(afield_t* field, Vector2 start, Vector2 goal);
void setup_start_field(afield_t* field, Vector2 start, Vector2 target);
Vector2 get_best_node(afield_t *field);
void collapse_node_at(afield_t *field, Vector2 node_pos);
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);
int adjusted_euclidean(Vector2 a, Vector2 b);
+275
View File
@@ -0,0 +1,275 @@
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "../include/astar.h"
void reset_field(afield_t *field, int** map) {
for (int r = 0; r < field->rows; r++) {
for (int c = 0; c < field->columns; c++) {
bool is_obstacle = map[r][c] != -1 ? true : false;
field->map[r][c] = (anode_t){
.parent = {-1, -1},
.h = -1,
.g = -1,
.f = -1,
.is_collapsed = false,
.is_obstacle = is_obstacle,
};
}
}
}
afield_t generate_astar_field(int rows, int columns, int** col_map) {
anode_t **map = calloc(sizeof(anode_t *), rows);
assert(map);
for (int r = 0; r < rows; r++) {
map[r] = (anode_t *)calloc(sizeof(anode_t), columns);
assert(map[r]);
}
afield_t field = {
rows = rows,
columns = columns,
map = map,
};
reset_field(&field, col_map);
return field;
}
int adjusted_euclidean(Vector2 a, Vector2 b) {
int dx = b.x - a.x;
int dy = b.y - a.y;
return (int)round(sqrt(dx * dx + dy * dy) * 10.0);
}
int octile_heuristic(Vector2 a, Vector2 b) {
int dx = abs((int)b.x - (int)a.x);
int dy = abs((int)b.y - (int)a.y);
int min_d = dx < dy ? dx : dy;
int max_d = dx > dy ? dx : dy;
return 14 * min_d + 10 * (max_d - min_d);
}
void update_node_values(afield_t *field, Vector2 target, Vector2 node_pos, Vector2 parent_pos) {
anode_t *node = &field->map[(int)node_pos.y][(int)node_pos.x];
int traversal_cost = (node_pos.x == parent_pos.x) || (node_pos.y == parent_pos.y) ? 10 : 14;
int new_g = field->map[(int)parent_pos.y][(int)parent_pos.x].g + traversal_cost;
if (node->parent.y != -1) {
// new potential cost
if (node->g <= new_g) {
return;
}
}
// set parent
node->parent = parent_pos;
// set values
node->g = new_g;
node->h = octile_heuristic(node_pos, target);
node->f = node->g + node->h;
}
bool is_valid_child(afield_t* field, Vector2 parent, Vector2 child) {
int child_x = child.x;
int child_y = child.y;
// if in bounds set note as parent
if (child_x < 0 || child_y < 0 || child_x >= field->columns || child_y >= field->rows) {
// printf("out of bounds node at: %d %d\n", child_x, child_y);
return false;
}
if (field->map[child_y][child_x].is_collapsed) { return false; }
if (field->map[child_y][child_x].is_obstacle) { return false; }
// dont allow diagonal passing trough obstacles
if (child.x == parent.x || child.y == parent.y) {
// no diagonal movement
return true;
}
Vector2 dir = Vector2Subtract(child, parent);
// printf("Direction: %i %i\n", (int)dir.x, (int)dir.y);
bool is_x_obstacle = field->map[(int)(parent.y+dir.y)][(int)parent.x].is_obstacle;
bool is_y_obstacle = field->map[(int)parent.y][(int)(parent.x+dir.x)].is_obstacle;
// printf("obstacles: %i %i\n", is_x_obstacle, is_y_obstacle);
if (is_x_obstacle || is_y_obstacle) {
return false;
}
return true;
}
int assign_node_as_parent(afield_t* field, Vector2 target, Vector2 node_pos) {
Vector2 offsets[3][3] = {
{(Vector2){-1.0f, -1.0f}, (Vector2){0.0f, -1.0f}, (Vector2){1.0f, -1.0f}},
{(Vector2){-1.0f, 0.0f}, (Vector2){0.0f, 0.0f}, (Vector2){1.0f, 0.0f}},
{(Vector2){-1.0f, 1.0f}, (Vector2){0.0f, 1.0f}, (Vector2){1.0f, 1.0f}},
};
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
if (x == 1 && y == 1) { continue; } // skip parent
int child_x = node_pos.x + offsets[y][x].x;
int child_y = node_pos.y + offsets[y][x].y;
if (!is_valid_child(field, node_pos, (Vector2) { child_x, child_y } )) {
continue;
}
if (child_x == (int)target.x && child_y == (int)target.y) {
// target found!!
field->map[(int)target.y][(int)target.x].is_collapsed = true;
field->map[(int)target.y][(int)target.x].parent = node_pos;
return 1;
}
update_node_values(field, target, (Vector2) {child_x, child_y}, node_pos);
}
}
return 0;
}
void collapse_node_at(afield_t *field, Vector2 node_pos) {
field->map[(int)node_pos.y][(int)node_pos.x].is_collapsed = true;
}
Vector2 get_best_node(afield_t *field) {
Vector2 best_pos = {-1, -1};
int best_f = __INT_MAX__;
int best_h = __INT_MAX__;
for (int y = 0; y < field->rows; y++) {
for (int x = 0; x < field->columns; x++) {
anode_t *test_node = &field->map[y][x];
if (test_node->f == -1) { continue; }
// TODO what if two nodes have same f and same heuristic? (symetric
// problem)
if ((
(test_node->f < best_f) ||
(test_node->f == best_f && test_node->h < best_h)
) &&
!test_node->is_collapsed
&&
!test_node->is_obstacle
) {
// new best
best_pos = (Vector2){x, y};
best_f = test_node->f;
best_h = test_node->h;
}
}
}
// will return -1 -1 when no uncollapsed node was found
return best_pos;
}
void get_a_star_path(afield_t *field, Vector2 start, Vector2 goal) {
// TODO: check if start or end node is obstacle
if (start.x == goal.x && start.y == goal.y) {
// no path required
return; // TODO: Return valid empty path
}
// 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;
// set values of surrounding nodes
assign_node_as_parent(field, goal, start);
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
}
}
void setup_start_field(afield_t* field, Vector2 start, Vector2 target) {
int heuristic = octile_heuristic(start, target);
field->map[(int)start.y][(int)start.x].g = 0;
field->map[(int)start.y][(int)start.x].h = heuristic;
field->map[(int)start.y][(int)start.x].f = heuristic;
}
void debug_print_field_collisions(afield_t* field) {
printf("\nAstar Collision:\n");
for (int r = 0; r < field->rows; r++) {
for (int c = 0; c < field->columns; c++) {
printf("%i", field->map[r][c].is_obstacle);
}
printf("\n");
}
printf("\n");
}
void debug_print_field(afield_t* field, bool show_parent) {
char* arrows[3][3] = {
{"", "", ""},
{"", "", ""},
{"", "", ""}
};
printf("\nAstar Field:\n");
for (int r = 0; r < field->rows; r++) {
for (int c = 0; c < field->columns; c++) {
printf(" |");
if (field->map[r][c].is_obstacle) {
printf("#");
}
// else if (field->map[r][c].is_collapsed) {
// 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(" ");
}
}
else {
printf("%2i", field->map[r][c].f);
}
}
printf("| ");
}
printf("\n");
}
printf("\n");
}
+70
View File
@@ -0,0 +1,70 @@
#include <raylib.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "../include/astar.h"
int** create_array(int x, int y) {
int rows = y, cols = x;
int** myArray = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
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];
}
}
return myArray;
}
int main() {
int** dyn_array = create_array(10, 10);
afield_t field = generate_astar_field(10, 10, dyn_array);
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 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, true);
printf("FOUND TARGET!\n");
return 0;
}
}
}
Executable
BIN
View File
Binary file not shown.