diff --git a/config/boids.ini b/config/boids.ini index d685a42c6b..dd9896ea55 100644 --- a/config/boids.ini +++ b/config/boids.ini @@ -1,68 +1,66 @@ [base] +package = ocean env_name = boids +policy_name = Policy +rnn_name = Recurrent [env] num_envs = 64 -num_boids = 64 +num_agents = 64 ; num_envs = 1 -; num_boids = 1 -margin_turn_factor = 0.0 -centering_factor = 0.00 -avoid_factor = 1.00 -matching_factor = 1.00 +; num_agents = 5 +report_interval = 1 +; margin_turn_factor = 1.0 +; cohesion_factor = 0.0048 +; separation_factor = 0.0128 +; alignment_factor = 0.02 +margin_turn_factor = 1.0 +cohesion_factor = 0.001 +separation_factor = 0.0 +alignment_factor = 0.0 [vec] -num_workers = 2 -num_envs = 2 -batch_size = auto +total_agents = 4096 +num_buffers = 8 +num_threads = 8 [train] total_timesteps = 100_000_000 gamma = 0.95 learning_rate = 0.025 minibatch_size = 16384 -; minibatch_size = 1 -; [sweep] -; method = protein -; metric = episode_length +[sweep] +method = Protein +metric = perf +sweep_only = margin_turn_factor, cohesion_factor, separation_factor, alignment_factor -; [sweep.train.total_timesteps] -; distribution = log_normal -; min = 1e6 -; max = 1e7 -; mean = 5e6 -; scale = 0.5 +[sweep.train.total_timesteps] +distribution = log_normal +min = 1e3 +max = 1e7 +scale = time -; [sweep.train.gamma] -; distribution = log_normal -; min = 0.9 -; max = 0.999 -; mean = 0.97 +[sweep.env.margin_turn_factor] +distribution = log_normal +min = 0.01 +max = 5.0 +scale = auto -; [sweep.train.gae_lambda] -; distribution = log_normal -; min = 0.7 -; max = 0.999 -; mean = 0.95 - -; [sweep.train.learning_rate] -; distribution = log_normal -; min = 0.0001 -; max = 0.001 -; mean = 0.00025 -; scale = 0.5 - -; [sweep.train.batch_size] -; min = 32768 -; max = 131072 -; mean = 65536 -; scale = 0.5 - -; [sweep.train.minibatch_size] -; min = 512 -; max = 2048 -; mean = 1024 -; scale = 0.5 +[sweep.env.cohesion_factor] +distribution = log_normal +min = 0.01 +max = 1 +scale = auto +[sweep.env.separation_factor] +distribution = log_normal +min = 0.01 +max = 1 +scale = auto +[sweep.env.alignment_factor] +distribution = log_normal +min = 0.01 +max = 1 +scale = auto diff --git a/ocean/boids/binding.c b/ocean/boids/binding.c index a3483d6520..65b4998249 100644 --- a/ocean/boids/binding.c +++ b/ocean/boids/binding.c @@ -1,24 +1,27 @@ #include "boids.h" +#define OBS_SIZE 512 // 64 boids * 8 obs per boid +#define NUM_ATNS 2 // Two discrete actions per boid +#define ACT_SIZES {3, 3} +#define OBS_TENSOR_T FloatTensor #define Env Boids -#include "../env_binding.h" +#include "vecenv.h" -static int my_init(Env* env, PyObject* args, PyObject* kwargs) { - env->num_boids = unpack(kwargs, "num_boids"); - env->report_interval = unpack(kwargs, "report_interval"); - env->margin_turn_factor = unpack(kwargs, "margin_turn_factor"); - env->centering_factor = unpack(kwargs, "centering_factor"); - env->avoid_factor = unpack(kwargs, "avoid_factor"); - env->matching_factor = unpack(kwargs, "matching_factor"); +void my_init(Env* env, Dict* kwargs) { + env->num_agents = (unsigned int)dict_get(kwargs, "num_agents")->value; + env->report_interval = (unsigned)dict_get(kwargs, "report_interval")->value; + env->margin_turn_factor = (float)dict_get(kwargs, "margin_turn_factor")->value; + env->cohesion_factor = (float)dict_get(kwargs, "cohesion_factor")->value; + env->separation_factor = (float)dict_get(kwargs, "separation_factor")->value; + env->alignment_factor = (float)dict_get(kwargs, "alignment_factor")->value; init(env); - return 0; } -static int my_log(PyObject* dict, Log* log) { - assign_to_dict(dict, "perf", log->perf); - assign_to_dict(dict, "score", log->score); - assign_to_dict(dict, "episode_return", log->episode_return); - assign_to_dict(dict, "episode_length", log->episode_length); - assign_to_dict(dict, "n", log->n); - return 0; +void my_log(Log* log, Dict* out) { + dict_set(out, "score", log->score); + dict_set(out, "margin_turn_reward", log->t_margin_turn_reward); + dict_set(out, "cohesion_reward", log->t_cohesion_reward); + dict_set(out, "separation_reward", log->t_separation_reward); + dict_set(out, "alignment_reward", log->t_alignment_reward); + dict_set(out, "n", log->n); } diff --git a/ocean/boids/boids.c b/ocean/boids/boids.c index 735f8e8c9e..f1f978b44a 100644 --- a/ocean/boids/boids.c +++ b/ocean/boids/boids.c @@ -1,40 +1,37 @@ // Standalone C demo for Boids environment -// Compile using: ./scripts/build_ocean.sh boids [local|fast] +// Compile using: ./scripts/build.sh boids [local|fast] // Run with: ./boids + #include #include "boids.h" +#include // --- Demo Configuration --- -#define NUM_BOIDS_DEMO 20 // Number of boids for the standalone demo -#define MAX_STEPS_DEMO 500 // Max steps per episode in the demo +#define num_agents_DEMO 32 // Number of boids for the standalone demo +#define REPORT_INTERVAL_DEMO 1000 // Report interval for the demo +#define MAX_STEPS_DEMO 10000 // Max steps per episode in the demo #define ACTION_SCALE 3.0f // Corresponds to action space [-3.0, 3.0] -// Dummy action generation: random velocity changes for each boid void generate_dummy_actions(Boids* env) { - for (unsigned int i = 0; i < env->num_boids; ++i) { - // Generate random floats in [-1, 1] range + for (unsigned int i = 0; i < env->num_agents; ++i) { float rand_vx = ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f; float rand_vy = ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f; - - // Scale to the action space [-ACTION_SCALE, ACTION_SCALE] env->actions[i * 2 + 0] = rand_vx * ACTION_SCALE; env->actions[i * 2 + 1] = rand_vy * ACTION_SCALE; } } void demo() { - // Initialize Boids environment struct Boids env = {0}; - env.num_boids = NUM_BOIDS_DEMO; + env.num_agents = num_agents_DEMO; + env.report_interval = REPORT_INTERVAL_DEMO; - // In the Python binding, these pointers are assigned from NumPy arrays. - // Here, we need to allocate them explicitly. - size_t obs_size = env.num_boids * 4; // num_boids * (x, y, vx, vy) - size_t act_size = env.num_boids * 2; // num_boids * (dvx, dvy) + size_t obs_size = env.num_agents * env.num_agents * 8; // 8 = (x, y, vx, vy, dx, dy, dvx, dvy) + size_t act_size = env.num_agents * 2; // the 2 = (dvx, dvy) env.observations = (float*)calloc(obs_size, sizeof(float)); env.actions = (float*)calloc(act_size, sizeof(float)); - env.rewards = (float*)calloc(env.num_boids, sizeof(float)); // Env-level reward + env.rewards = (float*)calloc(env.num_agents, sizeof(float)); // Env-level reward if (!env.observations || !env.actions || !env.rewards) { fprintf(stderr, "ERROR: Failed to allocate memory for demo buffers.\n"); @@ -57,7 +54,7 @@ void demo() { c_reset(&env); int total_steps = 0; - printf("Starting Boids demo with %d boids. Press ESC to exit.\n", env.num_boids); + printf("Starting Boids demo with %u boids. Press ESC to exit.\n", env.num_agents); while (!WindowShouldClose() && total_steps < MAX_STEPS_DEMO) { // Raylib function to check if ESC is pressed or window closed generate_dummy_actions(&env); diff --git a/ocean/boids/boids.h b/ocean/boids/boids.h index bf2bf6331e..f5682b48ac 100644 --- a/ocean/boids/boids.h +++ b/ocean/boids/boids.h @@ -1,32 +1,31 @@ #include -#include -#include #include -#include -#include +#include #include #include "raylib.h" +#define BOID_WIDTH 16.0f +#define BOID_HEIGHT 16.0f #define TOP_MARGIN 50 #define BOTTOM_MARGIN 50 #define LEFT_MARGIN 50 #define RIGHT_MARGIN 50 #define VELOCITY_CAP 5 -#define VISUAL_RANGE 20 -#define PROTECTED_RANGE 100 +#define VISUAL_RANGE 400 +#define PROTECTED_RANGE ((int)(1.5f * BOID_WIDTH)) #define WIDTH 1080 #define HEIGHT 720 -#define BOID_WIDTH 32 -#define BOID_HEIGHT 32 -#define BOID_TEXTURE_PATH "./resources/puffers_128.png" +#define BOID_TEXTURE_PATH "./resources/shared/puffers_128.png" +#define EPS 1e-8f // avoids div by zero in angle calc typedef struct { - float perf; float score; - float episode_return; - float episode_length; float n; + float t_margin_turn_reward; + float t_cohesion_reward; + float t_separation_reward; + float t_alignment_reward; } Log; typedef struct { @@ -40,27 +39,32 @@ typedef struct { Velocity velocity; } Boid; -typedef struct Client Client; typedef struct { - // an array of shape (num_boids, 4) with the 4 values correspoinding to (x, y, velocity x, velocity y) + float width; + float height; + Texture2D boid_texture; +} Client ; + +typedef struct { + // Flat array of shape (num_agents * 8) values: + // - Each boid has 8 values corresponding to (x, y, vx, vy, dx, dy, dvx, dvy) + // - The first 8 values are for the boid itself + // - All the other 8 values are for the other boids float* observations; - // an array of shape (num_boids, 2) with the 2 values correspoinding to (velocity x, velocity y) - float* actions; - // an array of shape (1) with the summed up reward for all boids - float* rewards; - unsigned char* terminals; // Not being used but is required by env_binding.h + float* actions; // size (num_agents, 2->(dvx, dvy)) + float* rewards; // size (num_agents) with per-boid rewards + float* terminals; Boid* boids; - unsigned int num_boids; + unsigned num_agents; float margin_turn_factor; - float centering_factor; - float avoid_factor; - float matching_factor; + float cohesion_factor; + float separation_factor; + float alignment_factor; unsigned tick; Log log; - Log* boid_logs; unsigned report_interval; Client* client; - + unsigned rng; // unused but required field for vecenv compatibility } Boids; static inline float flmax(float a, float b) { return a > b ? a : b; } @@ -68,48 +72,72 @@ static inline float flmin(float a, float b) { return a > b ? b : a; } static inline float flclip(float x,float lo,float hi) { return flmin(hi,flmax(lo,x)); } static inline float rndf(float lo,float hi) { return lo + (float)rand()/(float)RAND_MAX*(hi-lo); } -static void respawn_boid(Boids *env, unsigned int i) { +static void spawn_boid(Boids *env, unsigned int i) { env->boids[i].x = rndf(LEFT_MARGIN, WIDTH - RIGHT_MARGIN); env->boids[i].y = rndf(BOTTOM_MARGIN, HEIGHT - TOP_MARGIN); env->boids[i].velocity.x = 0; env->boids[i].velocity.y = 0; - env->boid_logs[i] = (Log){0}; } void init(Boids *env) { - env->boids = (Boid*)calloc(env->num_boids, sizeof(Boid)); - env->boid_logs = (Log*)calloc(env->num_boids, sizeof(Log)); + if(env->num_agents < 1) { + printf("ERROR: num_agents must be bigger than 0\n"); + exit(1); + } + if (env->report_interval < 1) { + printf("ERROR: report_interval must be bigger than 0\n"); + exit(1); + } + env->boids = (Boid*)calloc(env->num_agents, sizeof(Boid)); env->log = (Log){0}; env->tick = 0; - for (unsigned current_indx = 0; current_indx < env->num_boids; current_indx++) { - env->boids[current_indx].x = rndf(LEFT_MARGIN, WIDTH - RIGHT_MARGIN); - env->boids[current_indx].y = rndf(BOTTOM_MARGIN, HEIGHT - TOP_MARGIN); - env->boids[current_indx].velocity.x = 0; - env->boids[current_indx].velocity.y = 0; - } + for (unsigned idx = 0; idx < env->num_agents; idx++) spawn_boid(env, idx); } static void compute_observations(Boids *env) { - unsigned base_indx; - int idx = 0; - for (unsigned i=0; inum_boids; i++) { - for (unsigned j=0; jnum_boids; j++) { - env->observations[idx++] = (env->boids[j].x - env->boids[i].x) / WIDTH; - env->observations[idx++] = (env->boids[j].y - env->boids[i].y) / HEIGHT; - env->observations[idx++] = (env->boids[j].velocity.x - env->boids[i].velocity.x) / VELOCITY_CAP; - env->observations[idx++] = (env->boids[j].velocity.y - env->boids[i].velocity.y) / VELOCITY_CAP; + float diff_x, diff_y; + for (unsigned i=0; inum_agents; i++) { + // observations for the current boid + env->observations[idx++] = env->boids[i].x / WIDTH; + env->observations[idx++] = env->boids[i].y / HEIGHT; + env->observations[idx++] = env->boids[i].velocity.x / VELOCITY_CAP; + env->observations[idx++] = env->boids[i].velocity.y / VELOCITY_CAP; + // zeros for relative observations since comparing to itself will always be 0 (dx, dy, dvx, dvy) + for (unsigned j=0; j<4; j++) { env->observations[idx++] = 0; } + + // observations for the other boids compared to the current boid + for (unsigned j=0; jnum_agents; j++) { + if (i == j) continue; + diff_x = env->boids[i].x - env->boids[j].x; + diff_y = env->boids[i].y - env->boids[j].y; + + env->observations[idx++] = env->boids[j].x / WIDTH; + env->observations[idx++] = env->boids[j].y / HEIGHT; + env->observations[idx++] = env->boids[j].velocity.x / VELOCITY_CAP; + env->observations[idx++] = env->boids[j].velocity.y / VELOCITY_CAP; + env->observations[idx++] = diff_x / WIDTH; + env->observations[idx++] = diff_y / HEIGHT; + env->observations[idx++] = (env->boids[i].velocity.x - env->boids[j].velocity.x) / VELOCITY_CAP; + env->observations[idx++] = (env->boids[i].velocity.y - env->boids[j].velocity.y) / VELOCITY_CAP; } } } +void apply_action(Boid* boid, float vx, float vy) { + boid->velocity.x = flclip(boid->velocity.x + vx, -VELOCITY_CAP, VELOCITY_CAP); + boid->velocity.y = flclip(boid->velocity.y + vy, -VELOCITY_CAP, VELOCITY_CAP); + boid->x = flclip(boid->x + boid->velocity.x, 0, WIDTH - BOID_WIDTH); + boid->y = flclip(boid->y + boid->velocity.y, 0, HEIGHT - BOID_HEIGHT); +} + void c_reset(Boids *env) { env->log = (Log){0}; env->tick = 0; - for (unsigned boid_indx = 0; boid_indx < env->num_boids; boid_indx++) { - respawn_boid(env, boid_indx); + for (unsigned boid_indx = 0; boid_indx < env->num_agents; boid_indx++) { + spawn_boid(env, boid_indx); } compute_observations(env); } @@ -118,40 +146,47 @@ void c_step(Boids *env) { Boid* current_boid; Boid observed_boid; float vis_vx_sum, vis_vy_sum, vis_x_sum, vis_y_sum, vis_x_avg, vis_y_avg, vis_vx_avg, vis_vy_avg; - float diff_x, diff_y, dist, protected_dist_sum, current_boid_reward; + float diff_x, diff_y, dist, current_boid_reward; + float margin_turn_reward, cohesion_reward, separation_reward, alignment_reward; + float protected_x_sum, protected_y_sum; + float rule_dx, rule_dy, rule_mag; unsigned visual_count, protected_count; bool manual_control = IsKeyDown(KEY_LEFT_SHIFT); float mouse_x = (float)GetMouseX(); float mouse_y = (float)GetMouseY(); env->tick++; - env->rewards[0] = 0; + env->rewards[0] = 0.0; env->log.score = 0; - for (unsigned current_indx = 0; current_indx < env->num_boids; current_indx++) { - // apply action + env->log.n = 0; + env->log.t_margin_turn_reward = 0; + env->log.t_cohesion_reward = 0; + env->log.t_separation_reward = 0; + env->log.t_alignment_reward = 0; + for (unsigned current_indx = 0; current_indx < env->num_agents; current_indx++) { current_boid = &env->boids[current_indx]; if (manual_control) { - current_boid->velocity.x = flclip(current_boid->velocity.x + (mouse_x - current_boid->x), -VELOCITY_CAP, VELOCITY_CAP); - current_boid->velocity.y = flclip(current_boid->velocity.y + (mouse_y - current_boid->y), -VELOCITY_CAP, VELOCITY_CAP); + apply_action(current_boid, (mouse_x - current_boid->x), (mouse_y - current_boid->y)); } else { - current_boid->velocity.x = flclip(current_boid->velocity.x + 2*env->actions[current_indx * 2 + 0], -VELOCITY_CAP, VELOCITY_CAP); - current_boid->velocity.y = flclip(current_boid->velocity.y + 2*env->actions[current_indx * 2 + 1], -VELOCITY_CAP, VELOCITY_CAP); + apply_action(current_boid, (env->actions[current_indx*2] - 1.0f), (env->actions[current_indx*2 + 1] - 1.0f)); } - current_boid->x = flclip(current_boid->x + current_boid->velocity.x, 0, WIDTH - BOID_WIDTH); - current_boid->y = flclip(current_boid->y + current_boid->velocity.y, 0, HEIGHT - BOID_HEIGHT); // reward calculation - current_boid_reward = 0.0f, protected_dist_sum = 0.0f, protected_count = 0.0f; - visual_count = 0.0f, vis_vx_sum = 0.0f, vis_vy_sum = 0.0f, vis_x_sum = 0.0f, vis_y_sum = 0.0f; - for (unsigned observed_indx = 0; observed_indx < env->num_boids; observed_indx++) { + current_boid_reward = 0.0f; + margin_turn_reward = 0.0f; cohesion_reward = 0.0f; separation_reward = 0.0f; alignment_reward = 0.0f; + protected_count = 0; visual_count = 0; + vis_vx_sum = 0.0f; vis_vy_sum = 0.0f; vis_x_sum = 0.0f; vis_y_sum = 0.0f; + protected_x_sum = 0.0f; protected_y_sum = 0.0f; + for (unsigned observed_indx = 0; observed_indx < env->num_agents; observed_indx++) { if (current_indx == observed_indx) continue; observed_boid = env->boids[observed_indx]; diff_x = current_boid->x - observed_boid.x; diff_y = current_boid->y - observed_boid.y; dist = sqrtf(diff_x*diff_x + diff_y*diff_y); if (dist < PROTECTED_RANGE) { - protected_dist_sum += (PROTECTED_RANGE - dist); protected_count++; + protected_x_sum += diff_x; + protected_y_sum += diff_y; } else if (dist < VISUAL_RANGE) { vis_x_sum += observed_boid.x; vis_y_sum += observed_boid.y; @@ -161,57 +196,48 @@ void c_step(Boids *env) { } } if (protected_count > 0) { - //current_boid_reward -= fabsf(protected_dist_sum / protected_count) * env->avoid_factor; - current_boid_reward -= flclip(protected_count/5.0, 0.0f, 1.0f) * env->avoid_factor; + rule_mag = sqrtf(protected_x_sum*protected_x_sum + protected_y_sum*protected_y_sum) + EPS; + separation_reward -= rule_mag * env->separation_factor; } if (visual_count) { - vis_x_avg = vis_x_sum / visual_count; - vis_y_avg = vis_y_sum / visual_count; + vis_x_avg = vis_x_sum / visual_count; + vis_y_avg = vis_y_sum / visual_count; + cohesion_reward -= fabsf(vis_x_avg - current_boid->x) * env->cohesion_factor; + cohesion_reward -= fabsf(vis_y_avg - current_boid->y) * env->cohesion_factor; + vis_vx_avg = vis_vx_sum / visual_count; vis_vy_avg = vis_vy_sum / visual_count; - - current_boid_reward -= fabsf(vis_vx_avg - current_boid->velocity.x) * env->matching_factor; - current_boid_reward -= fabsf(vis_vy_avg - current_boid->velocity.y) * env->matching_factor; - current_boid_reward -= fabsf(vis_x_avg - current_boid->x) * env->centering_factor; - current_boid_reward -= fabsf(vis_y_avg - current_boid->y) * env->centering_factor; - } - if (current_boid->y < TOP_MARGIN || current_boid->y > HEIGHT - BOTTOM_MARGIN) { - current_boid_reward -= env->margin_turn_factor; - } else { - current_boid_reward += env->margin_turn_factor; + alignment_reward -= fabsf(vis_vx_avg - current_boid->velocity.x) * env->alignment_factor; + alignment_reward -= fabsf(vis_vy_avg - current_boid->velocity.y) * env->alignment_factor; } - if (current_boid->x < LEFT_MARGIN || current_boid->x > WIDTH - RIGHT_MARGIN) { - current_boid_reward -= env->margin_turn_factor; - } else { - current_boid_reward += env->margin_turn_factor; + + if (current_boid->y < TOP_MARGIN || current_boid->x < LEFT_MARGIN + || current_boid->y + BOID_HEIGHT > HEIGHT - BOTTOM_MARGIN + || current_boid->x + BOID_WIDTH > WIDTH - RIGHT_MARGIN) { + margin_turn_reward -= env->margin_turn_factor; } + current_boid_reward = margin_turn_reward + cohesion_reward + separation_reward + alignment_reward; + // Normalization - // env->rewards[current_indx] = current_boid_reward / 15.0f; - // printf("current_boid_reward: %f\n", current_boid_reward); - env->rewards[current_indx] = current_boid_reward / 2.0f; + // env->rewards[current_indx] = current_boid_reward / 5.0f; + // env->rewards[current_indx] = current_boid_reward / 205.0f; + env->rewards[current_indx] = current_boid_reward / 64.0f; //log updates if (env->tick == env->report_interval) { - env->log.score += env->rewards[current_indx]; - env->log.n += 1.0f; - - /* clear per-boid log for next episode */ - // env->boid_logs[boid_indx] = (Log){0}; - env->tick = 0; + env->log.score += env->rewards[current_indx]; + env->log.t_margin_turn_reward += margin_turn_reward; + env->log.t_cohesion_reward += cohesion_reward; + env->log.t_separation_reward += separation_reward; + env->log.t_alignment_reward += alignment_reward; + env->log.n += 1.0f; } } - //env->log.score /= env->num_boids; + if (env->tick == env->report_interval) env->tick = 0; compute_observations(env); } -typedef struct Client Client; -struct Client { - float width; - float height; - Texture2D boid_texture; -}; - void c_close_client(Client* client) { UnloadTexture(client->boid_texture); CloseWindow(); @@ -220,7 +246,6 @@ void c_close_client(Client* client) { void c_close(Boids* env) { free(env->boids); - free(env->boid_logs); if (env->client != NULL) { c_close_client(env->client); } @@ -228,26 +253,26 @@ void c_close(Boids* env) { Client* make_client(Boids* env) { Client* client = (Client*)calloc(1, sizeof(Client)); - + client->width = WIDTH; client->height = HEIGHT; - + InitWindow(WIDTH, HEIGHT, "PufferLib Boids"); SetTargetFPS(60); - + if (!IsWindowReady()) { TraceLog(LOG_ERROR, "Window failed to initialize\n"); free(client); return NULL; } - + client->boid_texture = LoadTexture(BOID_TEXTURE_PATH); if (client->boid_texture.id == 0) { TraceLog(LOG_ERROR, "Failed to load texture: %s", BOID_TEXTURE_PATH); c_close_client(client); return NULL; } - + return client; } @@ -259,38 +284,33 @@ void c_render(Boids* env) { return; } } - - if (!WindowShouldClose() && IsWindowReady()) { - if (IsKeyDown(KEY_ESCAPE)) { - exit(0); - } - - BeginDrawing(); - ClearBackground((Color){6, 24, 24, 255}); - - for (unsigned boid_indx = 0; boid_indx < env->num_boids; boid_indx++) { - DrawTexturePro( - env->client->boid_texture, - (Rectangle){ - (env->boids[boid_indx].velocity.x > 0) ? 0 : 128, - 0, - 128, - 128, - }, - (Rectangle){ - env->boids[boid_indx].x, - env->boids[boid_indx].y, - BOID_WIDTH, - BOID_HEIGHT - }, - (Vector2){0, 0}, - 0, - WHITE - ); - } - - EndDrawing(); - } else { + if (WindowShouldClose() || !IsWindowReady()) { TraceLog(LOG_WARNING, "Window is not ready or should close"); + return; + } + if (IsKeyDown(KEY_ESCAPE)) exit(0); + + BeginDrawing(); + ClearBackground((Color){6, 24, 24, 255}); + for (unsigned boid_indx = 0; boid_indx < env->num_agents; boid_indx++) { + DrawTexturePro( + env->client->boid_texture, + (Rectangle){ + (env->boids[boid_indx].velocity.x > 0) ? 0.0f : 128.0f, + 0.0f, + 128.0f, + 128.0f, + }, + (Rectangle){ + env->boids[boid_indx].x, + env->boids[boid_indx].y, + BOID_WIDTH, + BOID_HEIGHT + }, + (Vector2){0.0f, 0.0f}, + 0, + WHITE + ); } + EndDrawing(); }