1 #include <nm_core.h>
2 #include <nm_string.h>
3 #include <nm_vector.h>
4 #include <nm_window.h>
5 #include <nm_ncurses.h>
6 
7 #include <time.h>
8 
9 #define NM_SI_ENEMIE_COUNT  60
10 #define NM_SI_ENEMIE_INROW  10
11 #define NM_SI_EMEMIE_ROTATE 10
12 #define NM_SI_DELAY         20000
13 #define NM_SI_BULLETS       200
14 #define NM_SI_BONIS_DELAY   1000
15 #define NM_SI_GAIN_COUNT    500
16 
nm_si_game(void)17 static void nm_si_game(void)
18 {
19     typedef enum {
20         NM_SI_SHIP_1,
21         NM_SI_SHIP_2,
22         NM_SI_SHIP_3,
23         NM_SI_BULLET,
24         NM_SI_PLAYER,
25         NM_SI_BONUS
26     } nm_si_type_t;
27 
28     typedef struct {
29         int pos_x;
30         int pos_y;
31         int health;
32         bool hit;
33         nm_si_type_t type;
34     } nm_si_t;
35 
36     static bool si_player_init = false;
37     static size_t bullets_cnt;
38     static nm_si_t player;
39     static size_t score;
40     static size_t level;
41     static size_t speed;
42     static size_t gain;
43 
44     bool play = true, change_dir = false, next = false;
45     int max_x, max_y, start_x, start_y;
46     nm_vect_t pl_bullets = NM_INIT_VECT;
47     nm_vect_t en_bullets = NM_INIT_VECT;
48     nm_vect_t enemies = NM_INIT_VECT;
49     nm_str_t info = NM_INIT_STR;
50     bool bonus_active = false;
51     bool gain_active = false;
52     size_t gain_counter = 0;
53     int direction = 1, mch;
54     int b_direction = 1;
55     uint64_t iter = 0;
56     nm_si_t bonus;
57 
58     const char *shape_player = "<^>";
59     const char *shape_ship0  = "#v#";
60     const char *shape_ship1  = ")v(";
61     const char *shape_ship2  = "]v[";
62     const char *shape_dead   = "x-x";
63     const char *shape_hit    = ">-<";
64     const char *shape_win    = "^_^";
65     const char *shape_bonus  = "(?)";
66 
67     nodelay(action_window, TRUE);
68     getmaxyx(action_window, max_y, max_x);
69 
70     bonus = (nm_si_t) {
71         .pos_x = 1,
72         .pos_y = 3,
73         .health = 1,
74         .hit = false,
75         .type = NM_SI_BONUS
76     };
77 
78     if (!si_player_init) {
79         player = (nm_si_t) {
80             .pos_x = max_x / 2,
81             .pos_y = max_y - 2,
82             .health = 5,
83             .hit = false,
84             .type = NM_SI_PLAYER
85         };
86         score = 0, bullets_cnt = NM_SI_BULLETS;
87         si_player_init = true;
88         speed = NM_SI_EMEMIE_ROTATE;
89         level = 0;
90         gain = 0;
91     } else {
92         level++;
93         bullets_cnt += (NM_SI_BULLETS - 50);
94         if (speed > 1) {
95             speed--;
96         }
97     }
98     start_x = (max_x / 5);
99     start_y = 5;
100 
101     for (size_t n = 0; n < NM_SI_ENEMIE_COUNT; n++) {
102         nm_si_type_t type = (n < 10) ? NM_SI_SHIP_3 :
103                             (n >= 10 && n < 30) ? NM_SI_SHIP_2 : NM_SI_SHIP_1;
104         size_t health = (n < 10) ? 3 :
105                             (n >= 10 && n < 30) ? 2 : 1;
106         nm_si_t ship = (nm_si_t) {
107             .pos_x = start_x,
108             .pos_y = start_y,
109             .type = type,
110             .hit = false,
111             .health = health
112         };
113 
114         nm_vect_insert(&enemies, &ship, sizeof(nm_si_t), NULL);
115         start_x += 5;
116         if (!((n + 1) % NM_SI_ENEMIE_INROW)) {
117             start_y++;
118             start_x = (max_x / 5);
119         }
120     }
121 
122     while (play) {
123         int ch = wgetch(action_window);
124 
125         werase(action_window);
126         nm_str_format(&info, "Level %zu [score: %zu hp: %d ammo: %zu ap: %zu]",
127                 level, score, player.health, bullets_cnt, gain);
128         nm_init_action(info.data);
129 
130         switch (ch) {
131         case KEY_LEFT:
132         case NM_KEY_A:
133             if (player.pos_x > 1) {
134                 player.pos_x -= 1;
135             }
136             break;
137         case KEY_RIGHT:
138         case NM_KEY_D:
139             if (player.pos_x < max_x - 4) {
140                 player.pos_x += 1;
141             }
142             break;
143         case NM_KEY_S:
144             if (gain && !gain_active) {
145                 gain_counter = NM_SI_GAIN_COUNT;
146                 gain_active = true;
147                 gain--;
148             }
149             break;
150         case ' ':
151             {
152                 nm_si_t bullet = (nm_si_t) {
153                     .pos_x = player.pos_x + 1,
154                     .pos_y = max_y - 3,
155                     .health = (gain_active) ? 3 : 1,
156                     .hit = false,
157                     .type = NM_SI_BULLET,
158                 };
159 
160                 if (bullets_cnt) {
161                     nm_vect_insert(&pl_bullets, &bullet, sizeof(nm_si_t), NULL);
162                     bullets_cnt--;
163                 }
164             }
165             break;
166         case NM_KEY_Q:
167             play = false;
168             break;
169         case ERR:
170         default:
171             break;
172         }
173 
174         if (!enemies.n_memb) {
175             play = false;
176             next = true;
177             mvwaddstr(action_window, player.pos_y, player.pos_x, shape_win);
178             break;
179         }
180 
181         for (size_t n = 0; n < enemies.n_memb; n++) {
182             nm_si_t *e = nm_vect_at(&enemies, n);
183 
184             for (size_t nb = 0; nb < pl_bullets.n_memb; nb++) {
185                 nm_si_t *b = nm_vect_at(&pl_bullets, nb);
186 
187                 if (((b->pos_x >= e->pos_x && b->pos_x <= e->pos_x + 2)) &&
188                         (e->pos_y == b->pos_y)) {
189                     size_t hp_hit = e->health;
190                     e->health -= b->health;
191                     b->health -= hp_hit;
192                     if (e->health <= 0) {
193                         switch (e->type) {
194                             case NM_SI_SHIP_1:
195                                 score += 10;
196                                 break;
197                             case NM_SI_SHIP_2:
198                                 score += 20;
199                                 break;
200                             case NM_SI_SHIP_3:
201                                 score += 30;
202                                 break;
203                             default:
204                                 break;
205                         }
206                     } else {
207                         e->hit = true;
208                         switch (e->type) {
209                             case NM_SI_SHIP_1:
210                                 score += 1;
211                                 break;
212                             case NM_SI_SHIP_2:
213                                 score += 2;
214                                 break;
215                             case NM_SI_SHIP_3:
216                                 score += 3;
217                                 break;
218                             default:
219                                 break;
220                         }
221                     }
222                     if (b->health <= 0) {
223                         nm_vect_delete(&pl_bullets, nb, NULL);
224                         nb--;
225                     }
226                     break;
227                 }
228             }
229 
230             if (e->health > 0) {
231                 bool can_shoot = true;
232                 const char *shape;
233                 switch (e->type) {
234                 case NM_SI_SHIP_1:
235                     shape = shape_ship0;
236                     break;
237                 case NM_SI_SHIP_2:
238                     shape = shape_ship1;
239                     break;
240                 case NM_SI_SHIP_3:
241                     shape = shape_ship2;
242                     break;
243                 default:
244                     break;
245                 }
246 
247                 if (e->hit) {
248                     shape = shape_hit;
249                     e->hit = false;
250                 }
251 
252                 mvwaddstr(action_window, e->pos_y, e->pos_x, shape);
253                 if (iter % (speed + 1) == speed) {
254                     direction ? e->pos_x++ : e->pos_x--;
255                     if (!direction && e->pos_x == 1) {
256                         change_dir = true;
257                     } else if (direction && (e->pos_x == max_x - 4)) {
258                         change_dir = true;
259                     }
260                 }
261 
262                 for (size_t no = n; no < enemies.n_memb; no++) {
263                     nm_si_t *eo = nm_vect_at(&enemies, no);
264                     if (((eo->pos_y - 1 >= e->pos_y &&
265                           eo->pos_y - 1 <= e->pos_y + 5)) &&
266                           (e->pos_x == eo->pos_x)) {
267                         can_shoot = false;
268                         break;
269                     }
270                 }
271 
272                 if (can_shoot) {
273                     struct timespec ts;
274                     int r;
275 
276                     clock_gettime(CLOCK_MONOTONIC, &ts);
277                     srand((time_t) ts.tv_nsec);
278                     r = rand() % 1000;
279                     if (r > 498 && r < 500) { /* fire! */
280                         nm_si_t bullet = (nm_si_t) {
281                             .pos_x = e->pos_x + 1,
282                             .pos_y = e->pos_y + 1,
283                             .health = 0,
284                             .hit = false,
285                             .type = NM_SI_BULLET
286                         };
287                         nm_vect_insert(&en_bullets, &bullet,
288                                 sizeof(nm_si_t), NULL);
289                     }
290                 }
291 
292             } else {
293                 mvwaddstr(action_window, e->pos_y, e->pos_x, shape_dead);
294                 nm_vect_delete(&enemies, n, NULL);
295                 n--;
296             }
297         }
298 
299         if (enemies.n_memb && change_dir) {
300             for (size_t n = 0; n < enemies.n_memb; n++) {
301                 nm_si_t *e = nm_vect_at(&enemies, n);
302                 e->pos_y += 1;
303                 if (e->pos_y == max_y - 2) {
304                     play = false;
305                 }
306             }
307 
308             if (!direction) {
309                 direction = 1;
310             } else {
311                 direction = 0;
312             }
313             change_dir = false;
314         }
315 
316         for (size_t n = 0; n < pl_bullets.n_memb; n++) {
317             nm_si_t *b = nm_vect_at(&pl_bullets, n);
318             mvwaddch(action_window, b->pos_y, b->pos_x, gain_active ? '^' : '*');
319             b->pos_y--;
320 
321             if (b->pos_y == 1) {
322                 nm_vect_delete(&pl_bullets, n, NULL);
323                 n--;
324             }
325         }
326 
327         if (iter % (NM_SI_BONIS_DELAY + 1) == NM_SI_BONIS_DELAY &&
328                 !bonus_active) {
329             bonus_active = true;
330         }
331 
332         if (bonus_active) {
333             for (size_t n = 0; n < pl_bullets.n_memb; n++) {
334                 nm_si_t *b = nm_vect_at(&pl_bullets, n);
335 
336                 if (((b->pos_x >= bonus.pos_x && b->pos_x <= bonus.pos_x + 2)) &&
337                         (bonus.pos_y == b->pos_y)) {
338                     bonus.hit = true;
339                     b->health--;
340                     if (!b->health) {
341                         nm_vect_delete(&pl_bullets, n, NULL);
342                     }
343                     break;
344                 }
345             }
346 
347             if (bonus.hit) {
348                 mvwaddstr(action_window, bonus.pos_y, bonus.pos_x, shape_dead);
349                 bonus.hit = false;
350                 bonus_active = false;
351                 if (b_direction) {
352                     b_direction = 0;
353                     bonus.pos_x = max_x - 4;
354                 } else {
355                     b_direction = 1;
356                     bonus.pos_x = 1;
357                 }
358                 score += 100;
359                 switch (iter % 3) {
360                 case 0:
361                     bullets_cnt += 50;
362                     break;
363                 case 1:
364                     player.health++;
365                     break;
366                 case 2:
367                     gain++;
368                     break;
369                 }
370             } else {
371                 mvwaddstr(action_window, bonus.pos_y, bonus.pos_x, shape_bonus);
372                 if (iter % (speed + 1) == speed) {
373                     (b_direction) ? bonus.pos_x++ : bonus.pos_x--;
374                     if (!b_direction && bonus.pos_x == 1) {
375                         bonus_active = false;
376                         b_direction = 1;
377                     } else if (b_direction && (bonus.pos_x == max_x - 4)) {
378                         bonus_active = false;
379                         b_direction = 0;
380                     }
381                 }
382             }
383         }
384 
385         for (size_t n = 0; n < en_bullets.n_memb; n++) {
386             nm_si_t *b = nm_vect_at(&en_bullets, n);
387             mvwaddch(action_window, b->pos_y, b->pos_x, '|');
388 
389             if (((b->pos_x >= player.pos_x && b->pos_x <= player.pos_x + 2)) &&
390                     (player.pos_y == b->pos_y)) {
391                 player.health--;
392                 player.hit = true;
393             }
394 
395             b->pos_y++;
396 
397             if (b->pos_y == max_y - 1) {
398                 nm_vect_delete(&en_bullets, n, NULL);
399                 n--;
400             }
401         }
402 
403         if (player.health) {
404             if (player.hit) {
405                 mvwaddstr(action_window, player.pos_y, player.pos_x, shape_hit);
406                 player.hit = false;
407             } else {
408                 mvwaddstr(action_window, player.pos_y, player.pos_x, shape_player);
409             }
410         } else {
411             mvwaddstr(action_window, player.pos_y, player.pos_x, shape_dead);
412             play = false;
413         }
414 
415         wrefresh(action_window);
416 
417         usleep(NM_SI_DELAY);
418         iter++;
419         if (gain_active) {
420             gain_counter--;
421             if (!gain_counter) {
422                 gain_active = false;
423             }
424         }
425     }
426 
427     nodelay(action_window, FALSE);
428     NM_ERASE_TITLE(action, getmaxx(action_window));
429     nm_str_format(&info, "%s [score: %zu hp: %d ammo: %zu ap: %zu]",
430             next ? "(n)ext level, (q)uit" : "Game over, (q)uit, (r)eplay",
431             score, player.health, bullets_cnt, gain);
432     nm_init_action(info.data);
433 
434     nm_str_free(&info);
435     nm_vect_free(&pl_bullets, NULL);
436     nm_vect_free(&en_bullets, NULL);
437     nm_vect_free(&enemies, NULL);
438 
439     do {
440         mch = wgetch(action_window);
441     } while ((mch != 'n') && (mch != 'q') && (mch != 'r'));
442 
443     if (mch == 'n' && next) {
444         nm_si_game();
445     } else if (mch == 'r') {
446         si_player_init = false;
447         nm_si_game();
448     } else {
449         si_player_init = false;
450     }
451 }
452 
nm_print_nemu(void)453 void nm_print_nemu(void)
454 {
455     int ch;
456     size_t max_y = getmaxy(action_window);
457     const char *nemu[] = {
458         "            .x@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@o.           ",
459         "          .x@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@x.          ",
460         "         o@@@@@@@@@@@@@x@@@@@@@@@@@@@@@@@@@@@@@@@o         ",
461         "       .x@@@@@@@@x@@@@xx@@@@@@@@@@@@@@@@@@@@@@@@@@o        ",
462         "      .x@@@@@@@x. .x@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.       ",
463         "      x@@@@@ox@x..o@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.      ",
464         "     o@@@@@@xx@@@@@@@@@@@@@@@@@@@@@@@@x@@@@@@@@@@@@@o      ",
465         "    .x@@@@@@@@@@x@@@@@@@@@@@@@@@@@@@@@o@@@@@@@@@@@@@@.     ",
466         "    .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@x.@@@@@@@@@@@@@@o     ",
467         "    o@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@xx..o@@@@@@@@@@@@@x     ",
468         "    x@@@@@@@@@@@@@@@@@@xxoooooooo........oxxx@@@@@@@@x.    ",
469         "    x@@@@@@@@@@x@@@@@@x. .oxxx@@xo.       .oxo.o@@@@@x     ",
470         "    o@@@@@@@@x..o@@@@@o .xxx@@@o ..      .x@ooox@@@@@o     ",
471         "    .x@@@@@@@oo..x@@@@.  ...oo..         .oo. o@@@@@o.     ",
472         "     .x@@@@@@xoooo@@@@.                       x@@@x..      ",
473         "       o@@@@@@ooxx@@@@.                 .    .@@@x..       ",
474         "        .o@@@@@@o.x@@@.                 ..   o@@x.         ",
475         "         .x@@@@@@xx@@@.                 ..  .x@o           ",
476         "         o@@@@@@@o.x@@.                    .x@x.           ",
477         "         o@@@@@@@o o@@@xo.         .....  .x@x.            ",
478         "          .@@@@@x. .x@o.x@x..           .o@@@.             ",
479         "         .x@@@@@o.. o@o  .o@@xoo..    .o..@@o              ",
480         "          o@@@@oo@@xx@x.   .x@@@ooooooo. o@x.              ",
481         "         .o@@o..xx@@@@@xo.. o@x.         xx.               ",
482         "          .oo. ....ox@@@@@@xx@o          xo.               ",
483         "          ....       .o@@@@@@@@o        .@..               ",
484         "           ...         ox.ox@@@.        .x..               ",
485         "            ...        .x. .@@x.        .o                 ",
486         "              .         .o .@@o.         o                 ",
487         "                         o. x@@o         .                 ",
488         "                         .o o@@..        .                 ",
489         "                          ...@o..        .                 ",
490         "                           .x@xo.                          ",
491         "                            .x..                           ",
492         "                           .x....                          ",
493         "                           o@..xo                          ",
494         "                           o@o oo                          ",
495         "                           ..                              "
496     };
497 
498     for (size_t l = 3, n = 0; n < (max_y - 4) && n < nm_arr_len(nemu); n++, l++)
499         mvwprintw(action_window, l, 1, "%s", nemu[n]);
500 
501     ch = wgetch(action_window);
502     if (ch == NM_KEY_S) {
503         nm_si_game();
504     }
505 }
506 
507 /* vim:set ts=4 sw=4: */
508