]> git.lizzy.rs Git - dungeon_game.git/blob - plugins/game/game.c
94ad148d6930bd8266bcc4c415af76d34fb4f10f
[dungeon_game.git] / plugins / game / game.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <assert.h>
5 #include <ctype.h>
6 #include <signal.h>
7 #include <termios.h>
8 #include <math.h>
9 #include <pthread.h>
10 #include <string.h>
11 #include "game.h"
12
13 /* Shared variables */
14
15 struct color black = {0};
16
17 struct material wall;
18 struct material air;
19 struct material outside;
20
21 struct node map[MAP_WIDTH][MAP_HEIGHT];
22
23 struct list *entities = & (struct list) {
24         .element = &player,
25         .next = NULL,
26 };
27
28 /* Private variables */
29
30 struct entity *entity_collision_map[MAP_WIDTH][MAP_HEIGHT] = {{NULL}};
31
32 static bool running = true;
33 static double damage_overlay = 0.0;
34 static struct color damage_overlay_color;
35
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;
41
42 /* Helper functions */
43
44 struct color get_color(const char *str)
45 {
46         unsigned int r, g, b;
47         sscanf(str, "#%2x%2x%2x", &r, &g, &b);
48         return (struct color) {r, g, b};
49 }
50
51 void set_color(struct color color, bool bg)
52 {
53         printf("\e[%u;2;%u;%u;%um", bg ? 48 : 38, color.r, color.g, color.b);
54 }
55
56 void light_color(struct color *color, double light)
57 {
58         color->r *= light;
59         color->g *= light;
60         color->b *= light;
61 }
62
63 void mix_color(struct color *color, struct color other, double ratio)
64 {
65         double ratio_total = ratio + 1;
66
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;
70 }
71
72 void dir_to_xy(enum direction dir, int *x, int *y)
73 {
74         switch (dir) {
75                 case UP:
76                         (*y)--;
77                         break;
78                 case LEFT:
79                         (*x)--;
80                         break;
81                 case DOWN:
82                         (*y)++;
83                         break;
84                 case RIGHT:
85                         (*x)++;
86                         break;
87         }
88 }
89
90 struct list *add_element(struct list *list, void *element)
91 {
92         struct list **ptr;
93
94         for (ptr = &list; *ptr != NULL; ptr = &(*ptr)->next)
95                 ;
96
97         *ptr = malloc(sizeof(struct list));
98         (*ptr)->element = element;
99         (*ptr)->next = NULL;
100
101         return list;
102 }
103
104 int clamp(int v, int min, int max)
105 {
106         return v < min ? min : v > max ? max : v;
107 }
108
109 int max(int a, int b)
110 {
111         return a > b ? a : b;
112 }
113
114 int min(int a, int b)
115 {
116         return a < b ? a : b;
117 }
118
119 void *make_buffer(void *ptr, size_t size)
120 {
121         void *buf = malloc(size);
122         memcpy(buf, ptr, size);
123
124         return buf;
125 }
126
127 double calculate_dtime(struct timespec from, struct timespec to)
128 {
129         return (double) (to.tv_sec - from.tv_sec) + (double) (to.tv_nsec - from.tv_nsec) / 1000000000.0;
130 }
131
132 /*struct roman_conversion_rule
133 {
134         int number;
135         const char *symbol;
136 };*/
137
138 static struct
139 {
140         int number;
141         const char *symbol;
142 } roman_ct[13] = {
143         {1000, "M"},
144         {900, "CM"},
145         {500, "D"},
146         {400, "CD"},
147         {100, "C"},
148         {90, "XC"},
149         {50, "L"},
150         {40, "XL"},
151         {10, "X"},
152         {9, "IX"},
153         {5, "V"},
154         {4, "IV"},
155         {1, "I"}
156 };
157
158 void get_roman_numeral(int number, char **ptr, size_t *len)
159 {
160         *ptr = NULL;
161         *len = 0;
162
163         for (int i = 0; i < 13; i++) {
164                 while (number >= roman_ct[i].number) {
165                         number -= roman_ct[i].number;
166                         size_t old_len = *len;
167                         *len += 1 + i % 2;
168                         *ptr = realloc(*ptr, *len + 1);
169                         strcpy(*ptr + old_len, roman_ct[i].symbol);
170                 }
171         }
172 }
173
174 /* Game-related utility functions */
175
176 void quit()
177 {
178         running = false;
179 }
180
181 bool player_dead()
182 {
183         return player.health <= 0;
184 }
185
186 /* Map functions */
187
188 struct node get_node(int x, int y)
189 {
190         return is_outside(x, y) ? (struct node) {&outside} : map[x][y];
191 }
192
193 bool is_outside(int x, int y)
194 {
195         return x >= MAP_WIDTH || x < 0 || y >= MAP_HEIGHT || y < 0;
196 }
197
198 bool is_solid(int x, int y)
199 {
200         return get_node(x, y).material->solid;
201 }
202
203 /* Entity functions */
204
205 bool spawn(struct entity def, int x, int y, void *data)
206 {
207         if (is_solid(x, y))
208                 return false;
209
210         if (def.collide_with_entities && entity_collision_map[x][y])
211                 return false;
212
213         def.x = x;
214         def.y = y;
215
216         struct entity *entity = malloc(sizeof(struct entity));
217         *entity = def;
218
219         add_element(entities, entity);
220
221         if (entity->collide_with_entities)
222                 entity_collision_map[x][y] = entity;
223
224         if (entity->on_spawn)
225                 entity->on_spawn(entity, data);
226
227         return true;
228 }
229
230 bool move(struct entity *entity, int xoff, int yoff)
231 {
232         int x, y;
233
234         x = entity->x + xoff;
235         y = entity->y + yoff;
236
237         if (is_solid(x, y)) {
238                 if (entity->on_collide)
239                         entity->on_collide(entity, x, y);
240                 return false;
241         } else if (entity->collide_with_entities && entity_collision_map[x][y]) {
242                 if (entity->on_collide_with_entity)
243                         entity->on_collide_with_entity(entity, entity_collision_map[x][y]);
244                 return false;
245         } else {
246                 entity_collision_map[entity->x][entity->y] = NULL;
247                 entity->x = x;
248                 entity->y = y;
249                 entity_collision_map[entity->x][entity->y] = entity;
250                 return true;
251         }
252 }
253
254 void add_health(struct entity *entity, int health)
255 {
256         bool was_alive = entity->health > 0;
257
258         entity->health += health;
259
260         if (health < 0 && entity->on_damage)
261                 entity->on_damage(entity, -health);
262
263         if (entity->health > entity->max_health)
264                 entity->health = entity->max_health;
265         else if (entity->health <= 0 && was_alive && entity->on_death)
266                 entity->on_death(entity);
267 }
268
269 /* Register callback functions */
270
271 void register_air_function(struct generator_function func)
272 {
273         air_functions = add_element(air_functions, make_buffer(&func, sizeof(struct generator_function)));
274 }
275
276 void register_input_handler(unsigned char c, struct input_handler handler)
277 {
278         if (input_handlers[c])
279                 return;
280
281         input_handlers[c] = make_buffer(&handler, sizeof(struct input_handler));
282 }
283
284 void register_render_component(void (*callback)(struct winsize ws))
285 {
286         render_components = add_element(render_components, callback);
287 };
288
289 void register_globalstep(struct globalstep step)
290 {
291         globalsteps = add_element(globalsteps, make_buffer(&step, sizeof(struct globalstep)));
292 }
293
294 /* Player */
295
296 static void player_death(struct entity *self)
297 {
298         self->texture = "☠ ";
299 }
300
301 static void player_damage(struct entity *self, int damage)
302 {
303         damage_overlay += (double) damage * 0.5;
304 }
305
306 struct entity player = {
307         .name = "player",
308         .x = MAP_WIDTH / 2,
309         .y = MAP_HEIGHT / 2,
310         .color = {0},
311         .use_color = false,
312         .texture = "🙂",
313         .remove = false,
314         .meta = NULL,
315         .health = 10,
316         .max_health = 10,
317         .collide_with_entities = true,
318
319         .on_step = NULL,
320         .on_collide = NULL,
321         .on_collide_with_entity = NULL,
322         .on_spawn = NULL,
323         .on_remove = NULL,
324         .on_death = &player_death,
325         .on_damage = &player_damage,
326 };
327
328 /* Mapgen */
329
330 static void mapgen_set_air(int x, int y)
331 {
332         if (is_outside(x, y))
333                 return;
334
335         if (map[x][y].material == &air)
336                 return;
337
338         map[x][y] = (struct node) {&air};
339
340         for (struct list *ptr = air_functions; ptr != NULL; ptr = ptr->next) {
341                 struct generator_function *func = ptr->element;
342
343                 if (rand() % func->chance == 0)
344                         func->callback(x, y);
345         }
346 }
347
348 static void generate_room(int origin_x, int origin_y)
349 {
350         int left = 5 + rand() % 10;
351         int right = 5 + rand() % 10;
352
353         int up = 0;
354         int down = 0;
355
356         for (int x = -left; x <= right; x++) {
357                 if (x < 0) {
358                         up += rand() % 2;
359                         down += rand() % 2;
360                 } else {
361                         up -= rand() % 2;
362                         down -= rand() % 2;
363                 }
364
365                 for (int y = -up; y <= down; y++)
366                         mapgen_set_air(origin_x + x, origin_y + y);
367         }
368 }
369
370 static bool check_direction(int x, int y, enum direction dir)
371 {
372         if (dir % 2 == 0)
373                 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);
374         else
375                 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);
376 }
377
378 static void generate_corridor(int lx, int ly, enum direction ldir)
379 {
380         if (is_outside(lx, ly))
381                 return;
382
383         if (rand() % 200 == 0) {
384                 generate_room(lx, ly);
385                 return;
386         }
387
388         mapgen_set_air(lx, ly);
389
390         int x, y;
391
392         enum direction dir;
393         enum direction ret = (ldir + 2) % 4;
394
395         int limit = 50;
396
397         do {
398                 x = lx;
399                 y = ly;
400
401                 if (rand() % 3 > 1)
402                         dir = ldir;
403                 else
404                         dir = rand() % 4;
405
406                 dir_to_xy(dir, &x, &y);
407         } while (dir == ret || (! check_direction(x, y, dir) && --limit));
408
409         if (limit)
410                 generate_corridor(x, y, dir);
411
412         if (rand() % 20 == 0)
413                 generate_corridor(lx, ly, ldir);
414 }
415
416 static void generate_corridor_random(int x, int y)
417 {
418         enum direction dir = rand() % 4;
419
420         generate_corridor(x, y, dir);
421         generate_corridor(x, y, (dir + 2) % 4);
422 }
423
424 /* Rendering */
425
426 static bool render_color(struct color color, double light, bool bg, bool use_color)
427 {
428         if (light <= 0.0) {
429                 set_color(black, bg);
430                 return false;
431         } else if (use_color) {
432                 if (damage_overlay > 0.0)
433                         mix_color(&color, damage_overlay_color, damage_overlay * 2.0);
434
435                 light_color(&color, light);
436
437                 set_color(color, bg);
438                 return true;
439         } else {
440                 return true;
441         }
442 }
443
444 static void render_map(struct winsize ws)
445 {
446         int cols = ws.ws_col / 2 - LIGHT * 2;
447         int rows = ws.ws_row / 2 - LIGHT;
448
449         int cols_left = ws.ws_col - cols - (LIGHT * 2 + 1) * 2;
450         int rows_left = ws.ws_row - rows - (LIGHT * 2 + 1);
451
452         for (int i = 0; i < rows; i++)
453                 for (int i = 0; i < ws.ws_col; i++)
454                         printf(" ");
455
456         for (int y = -LIGHT; y <= LIGHT; y++) {
457                 for (int i = 0; i < cols; i++)
458                         printf(" ");
459
460                 for (int x = -LIGHT; x <= LIGHT; x++) {
461                         int map_x, map_y;
462
463                         map_x = x + player.x;
464                         map_y = y + player.y;
465
466                         struct node node = get_node(map_x, map_y);
467
468                         double dist = sqrt(x * x + y * y);
469                         double light = 1.0 - (double) dist / (double) LIGHT;
470
471                         render_color(node.material->color, light, true, true);
472
473                         struct entity *entity = render_entities[x + LIGHT][y + LIGHT];
474                         render_entities[x + LIGHT][y + LIGHT] = NULL;
475
476                         if (entity && render_color(entity->color, light, false, entity->use_color))
477                                 printf("%s", entity->texture);
478                         else
479                                 printf("  ");
480                 }
481
482                 set_color(black, true);
483
484                 for (int i = 0; i < cols_left; i++)
485                         printf(" ");
486         }
487
488         for (int i = 0; i < rows_left; i++)
489                 for (int i = 0; i < ws.ws_col; i++)
490                         printf(" ");
491 }
492
493 static void render()
494 {
495         printf("\e[2J");
496
497         struct winsize ws;
498         ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
499
500         for (struct list *ptr = render_components; ptr != NULL; ptr = ptr->next) {
501                 printf("\e[0m\e[0;0H");
502                 set_color(black, true);
503
504                 ((void (*)(struct winsize ws)) ptr->element)(ws);
505         }
506
507         fflush(stdout);
508 }
509
510 /* Input */
511
512 static void handle_interrupt(int signal)
513 {
514         (void) signal;
515
516         quit();
517 }
518
519 static void handle_input(unsigned char c)
520 {
521         struct input_handler *handler = input_handlers[c];
522
523         if (handler && (handler->run_if_dead || ! player_dead()))
524                 handler->callback();
525 }
526
527 static void *input_thread(void *unused)
528 {
529         (void) unused;
530
531         while (running)
532                 handle_input(tolower(fgetc(stdin)));
533
534         return NULL;
535 }
536
537 /* Main Game */
538
539 void game()
540 {
541         struct sigaction sa;
542         sa.sa_handler = &handle_interrupt;
543         sigaction(SIGINT, &sa, NULL);
544
545         generate_corridor_random(player.x, player.y);
546
547         for (int i = 0; i < 50; i++)
548                 generate_corridor_random(rand() % MAP_WIDTH, rand() % MAP_HEIGHT);
549
550         struct termios oldtio, newtio;
551         tcgetattr(STDIN_FILENO, &oldtio);
552         newtio = oldtio;
553         newtio.c_lflag &= ~(ICANON | ECHO);
554         tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
555
556         printf("\e[?1049h\e[?25l");
557
558         pthread_t input_thread_id;
559         pthread_create(&input_thread_id, NULL, &input_thread, NULL);
560
561         struct timespec ts, ts_old;
562         clock_gettime(CLOCK_REALTIME, &ts_old);
563
564         while (running) {
565                 clock_gettime(CLOCK_REALTIME, &ts);
566                 double dtime = calculate_dtime(ts_old, ts);
567                 ts_old = ts;
568
569                 bool dead = player_dead();
570
571                 for (struct list *ptr = globalsteps; ptr != NULL; ptr = ptr->next) {
572                         struct globalstep *step = ptr->element;
573
574                         if (step->run_if_dead || ! dead)
575                                 step->callback(dtime);
576                 }
577
578                 if (! dead && damage_overlay > 0.0) {
579                         damage_overlay -= dtime;
580
581                         if (damage_overlay < 0.0)
582                                 damage_overlay = 0.0;
583                 }
584
585                 for (struct list **ptr = &entities; *ptr != NULL; ) {
586                         struct entity *entity = (*ptr)->element;
587
588                         if (entity->remove) {
589                                 assert(entity != &player);
590                                 struct list *next = (*ptr)->next;
591
592                                 if (entity->on_remove)
593                                         entity->on_remove(entity);
594
595                                 if (entity->meta)
596                                         free(entity->meta);
597
598                                 if (entity->collide_with_entities)
599                                         entity_collision_map[entity->x][entity->y] = NULL;
600
601                                 free(entity);
602                                 free(*ptr);
603
604                                 *ptr = next;
605                                 continue;
606                         }
607
608                         int dx, dy;
609
610                         dx = entity->x - player.x;
611                         dy = entity->y - player.y;
612
613                         bool visible = abs(dx) <= LIGHT && abs(dy) <= LIGHT;
614
615                         if (visible)
616                                 render_entities[dx + LIGHT][dy + LIGHT] = entity;
617
618                         if (! dead && entity->on_step)
619                                 entity->on_step(entity, (struct entity_step_data) {
620                                         .dtime = dtime,
621                                         .visible = visible,
622                                         .dx = dx,
623                                         .dy = dy,
624                                 });
625
626                         ptr = &(*ptr)->next;
627                 }
628
629                 render();
630
631                 // there is no such thing as glfwSwapBuffers, so we just wait 1 / 60 seconds to prevent artifacts
632                 usleep(1000000 / 60);
633         }
634
635         printf("\e[?1049l\e[?25h");
636         tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
637 }
638
639 /* Initializer function */
640
641 __attribute__ ((constructor)) static void init()
642 {
643         srand(time(0));
644
645         wall = (struct material) {
646                 .solid = true,
647                 .color = get_color("#5B2F00"),
648         };
649
650         air = (struct material) {
651                 .solid = false,
652                 .color = get_color("#FFE027"),
653         };
654
655         outside = (struct material) {
656                 .solid = true,
657                 .color = black,
658         };
659
660         entity_collision_map[player.x][player.y] = &player;
661
662         for (int x = 0; x < MAP_WIDTH; x++)
663                 for (int y = 0; y < MAP_HEIGHT; y++)
664                         map[x][y] = (struct node) {&wall};
665
666         register_input_handler('q', (struct input_handler) {
667                 .run_if_dead = true,
668                 .callback = &quit,
669         });
670
671         register_render_component(&render_map);
672
673         damage_overlay_color = get_color("#F20000");
674 }
675
676 /* Use later */
677
678 /*
679 get_box_char(is_solid(x, y - 1), is_solid(x, y + 1), is_solid(x - 1, y), is_solid(x + 1, y));
680
681 const char *get_box_char(bool up, bool down, bool left, bool right)
682 {
683         if (left && right && ! up && ! down)
684                 return "\u2501\u2501";
685         else if (up && down && ! right && ! left)
686                 return "\u2503 ";
687         else if (down && right && ! up && ! left)
688                 return "\u250F\u2501";
689         else if (down && left && ! up && ! right)
690                 return "\u2513 ";
691         else if (up && right && ! down && ! left)
692                 return "\u2517\u2501";
693         else if (up && left && ! down && ! right)
694                 return "\u251B ";
695         else if (up && down && right && ! left)
696                 return "\u2523\u2501";
697         else if (up && down && left && ! right)
698                 return "\u252B ";
699         else if (down && left && right && ! up)
700                 return "\u2533\u2501";
701         else if (up && left && right && ! down)
702                 return "\u253B\u2501";
703         else if (up && down && left && right)
704                 return "\u254b\u2501";
705         else if (left && ! up && ! down && ! right)
706                 return "\u2578 ";
707         else if (up && ! down && ! left && ! right)
708                 return "\u2579 ";
709         else if (right && ! up && ! down && ! left)
710                 return "\u257A\u2501";
711         else if (down && ! up && ! left && ! right)
712                 return "\u257B ";
713         else if (! up && ! down && ! left && ! right)
714                 return "\u25AA ";
715         else
716                 return "??";
717 }
718 */