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