13 /* Shared variables */
15 struct color black = {0};
19 struct material outside;
21 struct node map[MAP_WIDTH][MAP_HEIGHT];
23 struct list *entities = & (struct list) {
28 /* Private variables */
30 struct entity *entity_collision_map[MAP_WIDTH][MAP_HEIGHT] = {{NULL}};
32 static bool running = true;
33 static double damage_overlay = 0.0;
34 static struct color damage_overlay_color;
36 static struct list *air_functions = NULL;
37 static struct input_handler *input_handlers[256] = {NULL};
38 static struct entity *render_entities[LIGHT * 2 + 1][LIGHT * 2 + 1];
39 static struct list *render_components = NULL;
40 static struct list *globalsteps = NULL;
42 /* Helper functions */
44 struct color get_color(const char *str)
47 sscanf(str, "#%2x%2x%2x", &r, &g, &b);
48 return (struct color) {r, g, b};
51 void set_color(struct color color, bool bg)
53 printf("\e[%u;2;%u;%u;%um", bg ? 48 : 38, color.r, color.g, color.b);
56 void light_color(struct color *color, double light)
63 void mix_color(struct color *color, struct color other, double ratio)
65 double ratio_total = ratio + 1;
67 color->r = (color->r + other.r * ratio) / ratio_total;
68 color->g = (color->g + other.g * ratio) / ratio_total;
69 color->b = (color->b + other.b * ratio) / ratio_total;
72 void dir_to_xy(enum direction dir, int *x, int *y)
90 struct list *add_element(struct list *list, void *element)
94 for (ptr = &list; *ptr != NULL; ptr = &(*ptr)->next)
97 *ptr = malloc(sizeof(struct list));
98 (*ptr)->element = element;
104 int clamp(int v, int min, int max)
106 return v < min ? min : v > max ? max : v;
109 int max(int a, int b)
111 return a > b ? a : b;
114 int min(int a, int b)
116 return a < b ? a : b;
119 void *make_buffer(void *ptr, size_t size)
121 void *buf = malloc(size);
122 memcpy(buf, ptr, size);
127 double calculate_dtime(struct timespec from, struct timespec to)
129 return (double) (to.tv_sec - from.tv_sec) + (double) (to.tv_nsec - from.tv_nsec) / 1000000000.0;
152 void get_roman_numeral(int number, char **ptr, size_t *len)
157 for (int i = 0; i < 13; i++) {
158 while (number >= roman_ct[i].number) {
159 number -= roman_ct[i].number;
160 size_t old_len = *len;
162 *ptr = realloc(*ptr, *len + 1);
163 strcpy(*ptr + old_len, roman_ct[i].symbol);
168 /* Game-related utility functions */
177 return player.health <= 0;
182 struct node get_node(int x, int y)
184 return is_outside(x, y) ? (struct node) {&outside} : map[x][y];
187 bool is_outside(int x, int y)
189 return x >= MAP_WIDTH || x < 0 || y >= MAP_HEIGHT || y < 0;
192 bool is_solid(int x, int y)
194 return get_node(x, y).material->solid;
197 /* Entity functions */
199 bool spawn(struct entity def, int x, int y, void *data)
204 if (def.collide_with_entities && entity_collision_map[x][y])
210 struct entity *entity = malloc(sizeof(struct entity));
213 add_element(entities, entity);
215 if (entity->collide_with_entities)
216 entity_collision_map[x][y] = entity;
218 if (entity->on_spawn)
219 entity->on_spawn(entity, data);
224 bool move(struct entity *entity, int xoff, int yoff)
228 x = entity->x + xoff;
229 y = entity->y + yoff;
231 if (is_solid(x, y)) {
232 if (entity->on_collide)
233 entity->on_collide(entity, x, y);
235 } else if (entity->collide_with_entities && entity_collision_map[x][y]) {
236 if (entity->on_collide_with_entity)
237 entity->on_collide_with_entity(entity, entity_collision_map[x][y]);
240 entity_collision_map[entity->x][entity->y] = NULL;
243 entity_collision_map[entity->x][entity->y] = entity;
248 void add_health(struct entity *entity, int health)
250 bool was_alive = entity->health > 0;
252 entity->health += health;
254 if (health < 0 && entity->on_damage)
255 entity->on_damage(entity, -health);
257 if (entity->health > entity->max_health)
258 entity->health = entity->max_health;
259 else if (entity->health <= 0 && was_alive && entity->on_death)
260 entity->on_death(entity);
263 /* Register callback functions */
265 void register_air_function(struct generator_function func)
267 air_functions = add_element(air_functions, make_buffer(&func, sizeof(struct generator_function)));
270 void register_input_handler(unsigned char c, struct input_handler handler)
272 if (input_handlers[c])
275 input_handlers[c] = make_buffer(&handler, sizeof(struct input_handler));
278 void register_render_component(void (*callback)(struct winsize ws))
280 render_components = add_element(render_components, callback);
283 void register_globalstep(struct globalstep step)
285 globalsteps = add_element(globalsteps, make_buffer(&step, sizeof(struct globalstep)));
290 static void player_death(struct entity *self)
292 self->texture = "☠";
295 static void player_damage(struct entity *self, int damage)
297 damage_overlay += (double) damage * 0.5;
300 struct entity player = {
311 .collide_with_entities = true,
315 .on_collide_with_entity = NULL,
318 .on_death = &player_death,
319 .on_damage = &player_damage,
324 static void mapgen_set_air(int x, int y, enum mg_context ctx)
326 if (is_outside(x, y))
329 if (map[x][y].material == &air)
332 map[x][y] = (struct node) {&air};
334 for (struct list *ptr = air_functions; ptr != NULL; ptr = ptr->next) {
335 struct generator_function *func = ptr->element;
337 if (rand() % func->chance == 0)
338 func->callback(x, y, ctx);
342 static void generate_room(int origin_x, int origin_y)
344 int left = 5 + rand() % 10;
345 int right = 5 + rand() % 10;
350 for (int x = -left; x <= right; x++) {
359 for (int y = -up; y <= down; y++)
360 mapgen_set_air(origin_x + x, origin_y + y, MG_CTX_ROOM);
364 static bool check_direction(int x, int y, enum direction dir)
367 return is_solid(x + 1, y) && is_solid(x - 1, y) && (is_solid(x, y + 1) || rand() % 3 > 1) && (is_solid(x, y - 1) || rand() % 3 > 1);
369 return is_solid(x, y + 1) && is_solid(x, y - 1) && (is_solid(x + 1, y) || rand() % 3 > 1) && (is_solid(x - 1, y) || rand() % 3 > 1);
372 static void generate_corridor(int lx, int ly, enum direction ldir)
374 if (is_outside(lx, ly))
377 if (rand() % 200 == 0) {
378 generate_room(lx, ly);
382 mapgen_set_air(lx, ly, MG_CTX_CORRIDOR);
387 enum direction ret = (ldir + 2) % 4;
400 dir_to_xy(dir, &x, &y);
401 } while (dir == ret || (! check_direction(x, y, dir) && --limit));
404 generate_corridor(x, y, dir);
406 if (rand() % 20 == 0)
407 generate_corridor(lx, ly, ldir);
410 static void generate_corridor_random(int x, int y)
412 enum direction dir = rand() % 4;
414 generate_corridor(x, y, dir);
415 generate_corridor(x, y, (dir + 2) % 4);
420 static bool render_color(struct color color, double light, bool bg, bool use_color)
423 set_color(black, bg);
425 } else if (use_color) {
426 if (damage_overlay > 0.0)
427 mix_color(&color, damage_overlay_color, damage_overlay * 2.0);
429 light_color(&color, light);
431 set_color(color, bg);
438 static void render_map(struct winsize ws)
440 int cols = ws.ws_col / 2 - LIGHT * 2;
441 int rows = ws.ws_row / 2 - LIGHT;
443 int cols_left = ws.ws_col - cols - (LIGHT * 2 + 1) * 2;
444 int rows_left = ws.ws_row - rows - (LIGHT * 2 + 1);
446 for (int i = 0; i < rows; i++)
447 for (int i = 0; i < ws.ws_col; i++)
450 for (int y = -LIGHT; y <= LIGHT; y++) {
451 for (int i = 0; i < cols; i++)
454 for (int x = -LIGHT; x <= LIGHT; x++) {
457 map_x = x + player.x;
458 map_y = y + player.y;
460 struct node node = get_node(map_x, map_y);
462 double dist = sqrt(x * x + y * y);
463 double light = 1.0 - (double) dist / (double) LIGHT;
465 render_color(node.material->color, light, true, true);
467 struct entity *entity = render_entities[x + LIGHT][y + LIGHT];
468 render_entities[x + LIGHT][y + LIGHT] = NULL;
470 if (entity && render_color(entity->color, light, false, entity->use_color))
471 printf("%s", entity->texture);
476 set_color(black, true);
478 for (int i = 0; i < cols_left; i++)
482 for (int i = 0; i < rows_left; i++)
483 for (int i = 0; i < ws.ws_col; i++)
492 ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
494 for (struct list *ptr = render_components; ptr != NULL; ptr = ptr->next) {
495 printf("\e[0m\e[0;0H");
496 set_color(black, true);
498 ((void (*)(struct winsize ws)) ptr->element)(ws);
506 static void handle_interrupt(int signal)
511 static void handle_input(unsigned char c)
513 struct input_handler *handler = input_handlers[c];
515 if (handler && (handler->run_if_dead || ! player_dead()))
519 static void *input_thread(void *unused)
522 handle_input(tolower(fgetc(stdin)));
532 sa.sa_handler = &handle_interrupt;
533 sigaction(SIGINT, &sa, NULL);
535 generate_corridor_random(player.x, player.y);
537 for (int i = 0; i < 50; i++)
538 generate_corridor_random(rand() % MAP_WIDTH, rand() % MAP_HEIGHT);
540 struct termios oldtio, newtio;
541 tcgetattr(STDIN_FILENO, &oldtio);
543 newtio.c_lflag &= ~(ICANON | ECHO);
544 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
546 printf("\e[?1049h\e[?25l");
548 pthread_t input_thread_id;
549 pthread_create(&input_thread_id, NULL, &input_thread, NULL);
551 struct timespec ts, ts_old;
552 clock_gettime(CLOCK_REALTIME, &ts_old);
555 clock_gettime(CLOCK_REALTIME, &ts);
556 double dtime = calculate_dtime(ts_old, ts);
559 bool dead = player_dead();
561 for (struct list *ptr = globalsteps; ptr != NULL; ptr = ptr->next) {
562 struct globalstep *step = ptr->element;
564 if (step->run_if_dead || ! dead)
565 step->callback(dtime);
568 if (! dead && damage_overlay > 0.0) {
569 damage_overlay -= dtime;
571 if (damage_overlay < 0.0)
572 damage_overlay = 0.0;
575 for (struct list **ptr = &entities; *ptr != NULL; ) {
576 struct entity *entity = (*ptr)->element;
578 if (entity->remove) {
579 assert(entity != &player);
580 struct list *next = (*ptr)->next;
582 if (entity->on_remove)
583 entity->on_remove(entity);
588 if (entity->collide_with_entities)
589 entity_collision_map[entity->x][entity->y] = NULL;
600 dx = entity->x - player.x;
601 dy = entity->y - player.y;
603 bool visible = abs(dx) <= LIGHT && abs(dy) <= LIGHT;
606 render_entities[dx + LIGHT][dy + LIGHT] = entity;
608 if (! dead && entity->on_step)
609 entity->on_step(entity, (struct entity_step_data) {
621 // there is no such thing as glfwSwapBuffers, so we just wait 1 / 60 seconds to prevent artifacts
622 usleep(1000000 / 60);
625 printf("\e[?1049l\e[?25h");
626 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
629 /* Initializer function */
631 __attribute__ ((constructor)) static void init()
635 wall = (struct material) {
637 .color = get_color("#5B2F00"),
640 air = (struct material) {
642 .color = get_color("#FFE027"),
645 outside = (struct material) {
650 entity_collision_map[player.x][player.y] = &player;
652 for (int x = 0; x < MAP_WIDTH; x++)
653 for (int y = 0; y < MAP_HEIGHT; y++)
654 map[x][y] = (struct node) {&wall};
656 register_input_handler('q', (struct input_handler) {
661 register_render_component(&render_map);
663 damage_overlay_color = get_color("#F20000");
669 get_box_char(is_solid(x, y - 1), is_solid(x, y + 1), is_solid(x - 1, y), is_solid(x + 1, y));
671 const char *get_box_char(bool up, bool down, bool left, bool right)
673 if (left && right && ! up && ! down)
674 return "\u2501\u2501";
675 else if (up && down && ! right && ! left)
677 else if (down && right && ! up && ! left)
678 return "\u250F\u2501";
679 else if (down && left && ! up && ! right)
681 else if (up && right && ! down && ! left)
682 return "\u2517\u2501";
683 else if (up && left && ! down && ! right)
685 else if (up && down && right && ! left)
686 return "\u2523\u2501";
687 else if (up && down && left && ! right)
689 else if (down && left && right && ! up)
690 return "\u2533\u2501";
691 else if (up && left && right && ! down)
692 return "\u253B\u2501";
693 else if (up && down && left && right)
694 return "\u254b\u2501";
695 else if (left && ! up && ! down && ! right)
697 else if (up && ! down && ! left && ! right)
699 else if (right && ! up && ! down && ! left)
700 return "\u257A\u2501";
701 else if (down && ! up && ! left && ! right)
703 else if (! up && ! down && ! left && ! right)