11 #include <sys/ioctl.h>
17 double damage_overlay = 0.0;
21 struct color black = {0, 0, 0};
25 struct material outside;
27 struct node map[MAP_WIDTH][MAP_HEIGHT];
30 struct list *entities = & (struct list) {
35 struct entity *entity_collision_map[MAP_WIDTH][MAP_HEIGHT] = {{NULL}};
37 struct list *air_functions = NULL;
39 struct input_handler *input_handlers[256] = {NULL};
46 struct color get_color(const char *str)
49 sscanf(str, "#%2x%2x%2x", &r, &g, &b);
50 return (struct color) {r, g, b};
53 bool is_outside(int x, int y)
55 return x >= MAP_WIDTH || x < 0 || y >= MAP_HEIGHT || y < 0;
58 struct node get_node(int x, int y)
60 return is_outside(x, y) ? (struct node) {&outside} : map[x][y];
63 bool is_solid(int x, int y)
65 return get_node(x, y).material->solid;
68 bool move(struct entity *entity, int xoff, int yoff)
76 if (entity->on_collide)
77 entity->on_collide(entity, x, y);
79 } else if (entity->collide_with_entities && entity_collision_map[x][y]) {
80 if (entity->on_collide_with_entity)
81 entity->on_collide_with_entity(entity, entity_collision_map[x][y]);
84 entity_collision_map[entity->x][entity->y] = NULL;
87 entity_collision_map[entity->x][entity->y] = entity;
92 void spawn(struct entity def, int x, int y)
97 if (def.collide_with_entities && entity_collision_map[x][y])
103 struct entity *entity = malloc(sizeof(struct entity));
106 add_element(entities, entity);
108 if (entity->collide_with_entities)
109 entity_collision_map[x][y] = entity;
111 if (entity->on_spawn)
112 entity->on_spawn(entity);
115 void add_health(struct entity *entity, int health)
117 bool was_alive = entity->health > 0;
119 entity->health += health;
121 if (health < 0 && entity->on_damage)
122 entity->on_damage(entity, -health);
124 if (entity->health > entity->max_health)
125 entity->health = entity->max_health;
126 else if (entity->health <= 0 && was_alive && entity->on_death)
127 entity->on_death(entity);
130 void add_score(int s)
137 return player.health <= 0;
140 struct list *add_element(struct list *list, void *element)
144 for (ptr = &list; *ptr != NULL; ptr = &(*ptr)->next)
147 *ptr = malloc(sizeof(struct list));
148 (*ptr)->element = element;
154 void register_air_function(struct generator_function func)
156 struct generator_function *buf = malloc(sizeof(struct generator_function));
159 air_functions = add_element(air_functions, buf);
162 void register_input_handler(unsigned char c, struct input_handler handler)
164 if (input_handlers[c])
167 struct input_handler *buf = malloc(sizeof(struct input_handler));
170 input_handlers[c] = buf;
175 static void player_death(struct entity *self)
177 self->texture = "☠ ";
180 static void player_damage(struct entity *self, int damage)
182 damage_overlay = (double) damage * 0.5;
187 static bool check_direction(int x, int y, int dir)
190 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);
192 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);
195 static void generate_corridor(int lx, int ly, int ldir, bool off)
197 if (is_outside(lx, ly))
201 if (off && rand() % 100 == 0)
205 map[lx][ly] = (struct node) {&air};
207 for (struct list *ptr = air_functions; ptr != NULL; ptr = ptr->next) {
208 struct generator_function *func = ptr->element;
210 if (! func->chance || rand() % func->chance == 0) {
211 func->callback(lx, ly);
216 int ret = (ldir + 2) % 4;
243 } while (dir == ret || (! check_direction(x, y, dir) && --limit));
246 generate_corridor(x, y, dir, off);
248 if (rand() % 20 == 0)
249 generate_corridor(lx, ly, ldir, true);
252 static void generate_corridor_random(int x, int y)
254 int dir = rand() % 4;
256 generate_corridor(x, y, dir, false);
257 generate_corridor(x, y, (dir + 2) % 4, false);
262 void set_color(struct color color, bool bg)
264 printf("\e[%u;2;%u;%u;%um", bg ? 48 : 38, color.r, color.g, color.b);
267 void light_color(struct color *color, double light)
274 void mix_color(struct color *color, struct color other, double ratio)
276 double ratio_total = ratio + 1;
278 color->r = (color->r + other.r * ratio) / ratio_total;
279 color->g = (color->g + other.g * ratio) / ratio_total;
280 color->b = (color->b + other.b * ratio) / ratio_total;
283 static bool render_color(struct color color, double light, bool bg)
286 set_color(black, bg);
289 if (damage_overlay > 0.0)
290 mix_color(&color, get_color("#F20000"), damage_overlay * 2.0);
292 light_color(&color, light);
294 set_color(color, bg);
299 static void render(render_entity_list entity_list)
301 printf("\e[2J\e[0;0H");
304 ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
306 int cols = ws.ws_col / 2 - LIGHT * 2;
307 int rows = ws.ws_row / 2 - LIGHT;
309 int cols_left = ws.ws_col - cols - (LIGHT * 2 + 1) * 2;
310 int rows_left = ws.ws_row - rows - (LIGHT * 2 + 1);
312 set_color(black, true);
314 for (int i = 0; i < rows; i++)
315 for (int i = 0; i < ws.ws_col; i++)
318 for (int y = -LIGHT; y <= LIGHT; y++) {
319 for (int i = 0; i < cols; i++)
322 for (int x = -LIGHT; x <= LIGHT; x++) {
325 map_x = x + player.x;
326 map_y = y + player.y;
328 struct node node = get_node(map_x, map_y);
330 double dist = sqrt(x * x + y * y);
331 double light = 1.0 - (double) dist / (double) LIGHT;
333 render_color(node.material->color, light, true);
335 struct entity *entity = entity_list[x + LIGHT][y + LIGHT];
337 if (entity && render_color(entity->color, light, false))
338 printf("%s", entity->texture);
343 set_color(black, true);
345 for (int i = 0; i < cols_left; i++)
349 for (int i = 0; i < rows_left + 1; i++)
350 for (int i = 0; i < ws.ws_col; i++)
353 printf("\e[0;0H\e[39m");
355 printf("\e[32m\e[3mScore:\e[23m %d\e[39m", score);
359 for (int i = 0; i < rows; i++)
362 printf("\t\e[1mInventory\e[22m\n\n");
363 printf("\t0x\t\e[3mNothing\e[23m\n");
367 for (int i = 0; i < ws.ws_row - 2; i++)
370 int hearts_cols = ws.ws_col / 2 - player.max_health;
372 for (int i = 0; i < hearts_cols; i++)
375 set_color((struct color) {255, 0, 0}, false);
377 for (int i = 0; i < player.max_health; i++) {
378 if (i >= player.health)
379 set_color(get_color("#5A5A5A"), false);
388 static void handle_input(unsigned char c)
390 struct input_handler *handler = input_handlers[c];
392 if (handler && (handler->run_if_dead || ! player_dead()))
398 static void handle_interrupt(int signal)
405 static void *input_thread(void *unused)
410 handle_input(tolower(fgetc(stdin)));
417 __attribute__ ((constructor)) static void init()
419 wall = (struct material) {
421 .color = get_color("#5B2F00"),
424 air = (struct material) {
426 .color = get_color("#FFE027"),
429 outside = (struct material) {
434 player = (struct entity) {
438 .color = get_color("#00FFFF"),
444 .collide_with_entities = true,
448 .on_collide_with_entity = NULL,
451 .on_death = &player_death,
452 .on_damage = &player_damage,
455 entity_collision_map[player.x][player.y] = &player;
457 for (int x = 0; x < MAP_WIDTH; x++)
458 for (int y = 0; y < MAP_HEIGHT; y++)
459 map[x][y] = (struct node) {&wall};
461 register_input_handler('q', (struct input_handler) {
472 sa.sa_handler = &handle_interrupt;
473 sigaction(SIGINT, &sa, NULL);
475 generate_corridor_random(player.x, player.y);
477 for (int i = 0; i < 50; i++)
478 generate_corridor_random(rand() % MAP_WIDTH, rand() % MAP_HEIGHT);
480 struct termios oldtio, newtio;
481 tcgetattr(STDIN_FILENO, &oldtio);
483 newtio.c_lflag &= ~(ICANON | ECHO);
484 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
486 printf("\e[?1049h\e[?25l");
488 pthread_t input_thread_id;
489 pthread_create(&input_thread_id, NULL, &input_thread, NULL);
491 struct timespec ts, ts_old;
492 clock_gettime(CLOCK_REALTIME, &ts_old);
495 clock_gettime(CLOCK_REALTIME, &ts);
496 double dtime = (double) (ts.tv_sec - ts_old.tv_sec) + (double) (ts.tv_nsec - ts_old.tv_nsec) / 1000000000.0;
499 bool dead = player_dead();
501 if (! dead && damage_overlay > 0.0)
502 damage_overlay -= dtime;
504 render_entity_list render_list = {{NULL}};
506 for (struct list **ptr = &entities; *ptr != NULL; ) {
507 struct entity *entity = (*ptr)->element;
509 if (entity->remove) {
510 assert(entity != &player);
511 struct list *next = (*ptr)->next;
513 if (entity->on_remove)
514 entity->on_remove(entity);
528 dx = entity->x - player.x;
529 dy = entity->y - player.y;
531 bool visible = abs(dx) <= LIGHT && abs(dy) <= LIGHT;
534 render_list[dx + LIGHT][dy + LIGHT] = entity;
536 if (! dead && entity->on_step)
537 entity->on_step(entity, (struct entity_step_data) {
549 // there is no such thing as glfwSwapBuffers, so we just wait 1 / 60 seconds to prevent artifacts
550 usleep(1000000 / 60);
553 printf("\e[?1049l\e[?25h");
554 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
560 get_box_char(is_solid(x, y - 1), is_solid(x, y + 1), is_solid(x - 1, y), is_solid(x + 1, y));
562 const char *get_box_char(bool up, bool down, bool left, bool right)
564 if (left && right && ! up && ! down)
565 return "\u2501\u2501";
566 else if (up && down && ! right && ! left)
568 else if (down && right && ! up && ! left)
569 return "\u250F\u2501";
570 else if (down && left && ! up && ! right)
572 else if (up && right && ! down && ! left)
573 return "\u2517\u2501";
574 else if (up && left && ! down && ! right)
576 else if (up && down && right && ! left)
577 return "\u2523\u2501";
578 else if (up && down && left && ! right)
580 else if (down && left && right && ! up)
581 return "\u2533\u2501";
582 else if (up && left && right && ! down)
583 return "\u253B\u2501";
584 else if (up && down && left && right)
585 return "\u254b\u2501";
586 else if (left && ! up && ! down && ! right)
588 else if (up && ! down && ! left && ! right)
590 else if (right && ! up && ! down && ! left)
591 return "\u257A\u2501";
592 else if (down && ! up && ! left && ! right)
594 else if (! up && ! down && ! left && ! right)