diff --git a/main.c b/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ee164f2bb3e5204c8f19604bc43d5d828699a806 --- /dev/null +++ b/main.c @@ -0,0 +1,387 @@ +/****************************************************************************** + +Copyright (c) Anzo. +Simple Terminal based Boids + +=============================================================================== + + This program is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later +version. + + This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with +this program. If not, see <https://www.gnu.org/licenses/>. + +=============================================================================== + +small c file with boids represented in terminal, with only the libc as +dependency. This was mostly done using Conrad Parker pseudocode boid +implementation (http://www.kfish.org/boids/pseudocode.html), and thus lacks +optimizations. + +=============================================================================== +COMPILING AND REQUIREMENTS : + +terminal must support truecolor. + +compile this file with linkage to math library (generally -lm) +` +gcc main.c -lm -o boids +` + +the executable takes two optional args : width and height of the board +example : +` +boids 50 120 +` + +Theres a bunch of defines down there that parametrizes the boids. tinkering +with them maybe fun idk +******************************************************************************/ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <time.h> +#include <fcntl.h> +#include <sys/param.h> +#include <string.h> +#include <math.h> + +int width; +int height; + +struct winsize w; + +int w_c; //"center" coordinates +int h_c; + +/* ++---------------------------------+ +| | h_c   | <-- your screen +|-------+-----------------+ | +| w_c | | | +| | |  | +| | content |   | +| | |  | +| +-----------------+  | +| | ++---------------------------------+ +*/ + +char* grid; +char* prevGrid; + +int DEBUG; +int PAUSE; + +void initFlushGrid() +{ + int h, w;//current h w + + int v,r,g,b; + + for (int i=0; i<width*height; i++) + { + h = (i/width) + h_c + 1; + w = (i%width) + w_c + 1; + + //int v = (!grid[i]) ? 0 : MIN(grid[i]*10 + 50, 255); //value + int r = (!grid[i]) ? 0 : MIN(grid[i]*35 + 0, 255); + int g = (!grid[i]) ? 0 : MIN(grid[i]*15 + 100, 255); + int b = (!grid[i]) ? 0 : MIN(grid[i]*10 + 75, 255); + printf("\033[%i;%iH\033[48;2;%i;%i;%im ", h, w, r, g, b); + } + + fflush(stdout); + return; +} + +void flushGrid() +{ + int h, w;//current h w + + for (int i=0; i<width*height; i++) + { + if (grid[i] != prevGrid[i]) + { + h = (i/width) + h_c + 1; + w = (i%width) + w_c + 1; + //int v = (!grid[i]) ? 0 : MIN(grid[i]*10 + 50, 255); //value + int r = (!grid[i]) ? 0 : MIN(grid[i]*35 + 0, 255); + int g = (!grid[i]) ? 0 : MIN(grid[i]*15 + 100, 255); + int b = (!grid[i]) ? 0 : MIN(grid[i]*10 + 75, 255); + + printf("\033[%i;%iH\033[48;2;%i;%i;%im ", h, w, r, g, b); + } + } + + printf("\033[0;0H"); + + fflush(stdout); + return; +} + +//random float between 0 and 1 +float randf() +{ + return (float) random() / RAND_MAX; +} + +float norm(float x, float y) +{ + return sqrtf(x*x + y*y); +} + +#define BOIDS_AMOUNT 500 +#define MAX_SPEED 0.4 +#define BOUNCE_BACK + +#define SEEING_DISTANCE 5 + +//rule 1 cst +#define TO_CENTER_FACTOR 0.01 + +//rule 2 cst +#define AWAY_DIST 0.2 +#define KEEP_ME_AWAY_FACTOR 2 + +//rule 3 cst +#define MATCH_VEL_FACTOR 0.125 + +/* a boid is a point + velocity*/ +typedef struct +{ + float x; + float y; + float vx; + float vy; +} boid; + +boid *boidsList; + +float boidDist(boid a, boid b) +{ + return norm(a.x - b.x, a.y - b.y); +} + +int inSight(boid a, boid b) +{ + return (boidDist(a,b) < (float)SEEING_DISTANCE) ? 1 : 0; +} + +//randomly put them boids in the board. +//with random init velocity. +void initBoids() +{ + boid* b; + float normb; + for (int i=0; i < BOIDS_AMOUNT; i++) + { + b = &boidsList[i]; + + boidsList[i].x = randf() * width; + boidsList[i].y = randf() * height; + + boidsList[i].vx = randf() * 2 - 1; + boidsList[i].vy = randf() * 2 - 1; + } + + //limit velocity + normb = norm(b->vx, b->vy); + if ( normb > MAX_SPEED) + { + b->vx = (b->vx / normb) * MAX_SPEED; + b->vy = (b->vy / normb) * MAX_SPEED; + } +} + +//apply the three rules of boid mvmt +void UpdateBoids() +{ + boid* b; + float normb; + + float r1vx; + float r1vy; + + float r2vx; + float r2vy; + + float r3vx; + float r3vy; + + int N = 0; //number of boids in sight + + for (int i=0; i < BOIDS_AMOUNT; i++) + { + b = &boidsList[i]; + + //rule 1 : move toward percieved center (no "seeing distance") + //----------------------------------------------------------- + N = 0; + r1vx = 0; + r1vy = 0; + + for (int j=0; j < BOIDS_AMOUNT; j++) + { + if (i==j){continue;} + if (inSight(*b, boidsList[j])) + { + r1vx += boidsList[j].x; + r1vy += boidsList[j].y; + N += 1; + } + } + + r1vx = r1vx / MAX(N, 1); + r1vy = r1vy / MAX(N, 1); + //here, vx and vy holds barycenter of all other boids + + r1vx = (r1vx - b->x) * TO_CENTER_FACTOR; + r1vy = (r1vy - b->y) * TO_CENTER_FACTOR; + // correct vector + + //rule 2 : try not to collide + //---------------------------------------------------------- + N = 0; + r2vx = 0; + r2vy = 0; + for (int j=0; j < BOIDS_AMOUNT; j++) + { + if (i==j){continue;} + normb = norm(b->x - boidsList[j].x, b->y - boidsList[j].y); + if ( normb < AWAY_DIST) + { + r2vx = (b->x - boidsList[j].x) * KEEP_ME_AWAY_FACTOR; + r2vy = (b->y - boidsList[j].y) * KEEP_ME_AWAY_FACTOR; + } + } + + //rule 3 : match velocity of peers (no seeing distance) + //--------------------------------------------------------- + N = 0; + r3vx = 0; + r3vy = 0; + + for (int j=0; j < BOIDS_AMOUNT; j++) + { + if (i==j){continue;} + if (inSight(*b, boidsList[j])) + { + r3vx += boidsList[j].vx; + r3vy += boidsList[j].vy; + N += 1; + } + } + + r3vx = (r3vx / MAX(N,1)) * MATCH_VEL_FACTOR; + r3vy = (r3vy / MAX(N,1)) * MATCH_VEL_FACTOR; + + //add velocity + b->vx += r1vx + r2vx + r3vx; + b->vy += r1vy + r2vy + r3vy; + + //limit velocity + normb = norm(b->vx, b->vy); + if ( normb > MAX_SPEED) + { + b->vx = (b->vx / normb) * MAX_SPEED; + b->vy = (b->vy / normb) * MAX_SPEED; + } + +#ifdef BOUNCE_BACK + //bounce back b inside board + if (b->x + b->vx > width || b->x + b->vx < 0) + {b->vx *= -1;} + + if (b->y + b->vy > height || b->y + b->vy < 0) + {b->vy *= -1;} + +#else + //warp pos b inside the board + if (b->x + b->vx > width) + {b->x = 0 + b->vx;} + + if (b->x + b->vx < 0) + {b->x = width + b->vx;} + + if (b->vy + b->y > height) + {b->y = height + b->vy;} + + if (b->y + b->vy < 0) + {b->y = height + b->vy;} +#endif + + //update pos + b->x += b->vx; + b->y += b->vy; + } +} +//reset the grid to 0, and +//add one to each cell of where a boid is. +void renderBoidsToGrid() +{ + for (int i = 0; i < width*height; i++) + {grid[i]=0;} + + int pos; // index in grid + for (int ib = 0; ib < BOIDS_AMOUNT ; ib++) + { + pos = ((int)boidsList[ib].y)*width + (int)boidsList[ib].x; + grid[pos] += 1; + //printf("grid[%i] : %i\n", pos, grid[pos]); + } +} + +int main(int argc, char** argv) +{ + DEBUG = 0; + + //init rng with seed + srandom(time(NULL)); + + //init grids + ioctl(0, TIOCGWINSZ, &w); + if (argc == 3) + { + width=atoi(argv[1]); + height=atoi(argv[2]); + } + else + { + width=w.ws_col; + height=w.ws_row; + } + + w_c = (w.ws_col - width) / 2; + h_c = (w.ws_row - height) / 2; + + grid = calloc((width*height),sizeof(char)); + prevGrid = calloc((width*height),sizeof(char)); + + //init boids + boidsList = calloc(BOIDS_AMOUNT, sizeof(boid)); + + printf("\033[1J"); + + initBoids(); + + renderBoidsToGrid(); + initFlushGrid(); + + while(1) + { + usleep(2500); + memcpy(prevGrid, grid, width*height*sizeof(char)); + UpdateBoids(); + renderBoidsToGrid(); + flushGrid(); + } +}