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