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