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