Thumbnail

rani/games.git

Clone URL: https://git.buni.party/rani/games.git

Viewing file on branch master

1#include <ncurses.h>
2#include <stdbool.h>
3#include <string.h>
4#include <stdlib.h>
5
6/* BEGIN CONFIG */
7#ifndef X
8#define X 64
9#endif /* X */
10
11#ifndef Y
12#define Y 32
13#endif /* Y */
14
15#ifndef SEED
16#include <time.h>
17#define SEED time(NULL)
18#endif /* SEED */
19
20#ifndef DELAY
21#define DELAY 100
22#endif /* DELAY */
23
24#ifndef PATH_BENDS
25#define PATH_BENDS 3
26#endif /* PATH_BENDS */
27
28#ifndef ATTACK_ANIMATION_DELAY
29#define ATTACK_ANIMATION_DELAY 35
30#endif /* ATTACK_ANIMATION_DELAY */
31
32#ifndef MAX_ENEMIES
33#define MAX_ENEMIES 256
34#endif /* MAX_ENEMIES */
35
36#ifndef MAX_TURRETS
37#define MAX_TURRETS 128
38#endif /* MAX_TURRETS */
39
40#ifndef STARTING_CASH
41#define STARTING_CASH 120
42#endif /* STARTING_CASH */
43
44#ifndef STARTING_LIVES
45#define STARTING_LIVES 25
46#endif /* STARTING_LIVES */
47
48#ifndef STARTING_ROUND
49#define STARTING_ROUND 1
50#endif /* STARTING_ROUND */
51/* END CONFIG */
52
53#define ARRLEN(a) (sizeof(a)/sizeof(*a))
54
55#define BIN1(a) (a & 1)
56#define BIN2(a,b) ((BIN1(a) << 1 ) | BIN1(b))
57#define BIN4(a,b,c,d) ((BIN2(a,b) << 2) | BIN2(c,d))
58#define BIN8(a,b,c,d,e,f,g,h) ((BIN4(a,b,c,d) << 4) | BIN4(e,f,g,h))
59
60#define CELL_PATH_MASK BIN8(0,0,0,0,1,1,0,0)
61#define INT_TO_CELL_PATH(i) (((i) & (CELL_PATH_MASK >> 2)) << 2)
62#define CELL_PATH_TO_INT(c) (((c) & CELL_PATH_MASK) >> 2)
63
64#define CELL_TURRET_MASK BIN8(1,1,1,1,0,0,0,0)
65#define INT_TO_CELL_TURRET(i) (((i) & (CELL_TURRET_MASK >> 4)) << 4)
66#define CELL_TURRET_TO_INT(c) (((c) & CELL_TURRET_MASK) >> 4)
67
68#define SHOP_STARTX (X + 2)
69#define SHOP_STARTY (1)
70
71#define SHOP_ID_TO_X(id) (SHOP_STARTX)
72#define SHOP_ID_TO_Y(id) ((id) * 2 + SHOP_STARTY + 2)
73
74#define Y_TO_SHOP_ID(y) (((y) - SHOP_STARTY) % 2 == 1 ? \
75 (-1) : \
76 (((y) - SHOP_STARTY - 2) / 2))
77
78
79enum color {
80 RED = 1,
81 GREEN,
82 YELLOW,
83 BLUE,
84 MAGENTA,
85 CYAN,
86};
87
88enum state {
89 RUNNING,
90 PAUSED,
91 GAME_OVER,
92 QUIT,
93};
94
95enum cell {
96 CELL_EMPTY = 0,
97 CELL_PATH = 1 << 0,
98 CELL_TURRET = 1 << 1,
99};
100
101enum cell_path {
102 CELL_PATH_UP,
103 CELL_PATH_DOWN,
104 CELL_PATH_LEFT,
105 CELL_PATH_RIGHT,
106};
107
108
109struct enemies {
110 struct enemy {
111 int x;
112 int y;
113 int count;
114 int ticks;
115 } enemies[MAX_ENEMIES];
116
117 int idx;
118 int last_round;
119 int spawned;
120 int killed;
121 int to_spawn;
122
123 int spawnx;
124 int spawny;
125};
126
127
128struct {
129 char * name;
130 char symbol;
131 int cost; // cost to purchase
132 int radius; // attack radius
133 int rsplash; // slash damage radius
134 int damage; // damage per attack
135 int dsplash; // splash damage
136 int stack; // amount of turrets per tile (-1 for attacking turrets)
137 int ticks; // ticks between attacks
138 int n_upgrades; // number of upgrades
139 struct {
140 int cost;
141 int radius;
142 int rsplash;
143 int damage;
144 int dsplash;
145 int ticks;
146 } upgrades[4];
147} turrets[] = {
148/* Name Sym Cost Rad RSplsh Dmg DSplsh Stack Tick nUpgr*/
149 {"Spikes", 'M', 25, 0, 0, 1, 0, 10, 1, 0,},
150 {"Gunner", '%', 100, 3, 0, 1, 0, -1, 5, 3, {
151 { 25, 4, 0, 1, 0, 5 },
152 { 50, 4, 0, 2, 0, 5 },
153 { 75, 4, 0, 3, 0, 4 },
154 }},
155 {"Sniper", '|', 125, 25, 0, 1, 0, -1, 40, 4, {
156 {100, 25, 0, 2, 0, 32 },
157 {125, 25, 0, 3, 0, 27 },
158 {200, 25, 0, 5, 0, 20 },
159 {400, 25, 0, 5, 0, 10 },
160 }},
161 {"Bomb Lobber", '&', 165, 3, 1, 1, 1, -1, 10, 3, {
162 { 50, 3, 2, 1, 1, 10 },
163 {100, 4, 2, 1, 2, 10 },
164 {150, 4, 2, 1, 2, 6 },
165 }},
166 {"Machine Gunner", '$', 400, 2, 0, 1, 0, -1, 3, 4, {
167 {200, 3, 0, 1, 0, 3 },
168 {400, 4, 0, 2, 0, 2 },
169 {800, 5, 0, 4, 0, 2 },
170 {8000, 5, 0, 4, 0, 1 },
171 }},
172};
173
174struct turrets {
175 struct spawned_turret {
176 int x;
177 int y;
178 int id;
179 int radius;
180 int rsplash;
181 int damage;
182 int dsplash;
183 int stack;
184 int ticks;
185 int level; // upgrade level
186 int kills;
187 } spawned[MAX_TURRETS];
188
189 int idx;
190} spawned_turrets;
191
192
193unsigned long ticks = 0;
194
195
196void enemies_push(struct enemies * enemies, int x, int y, int count, int ticks) {
197 int idx = enemies->idx;
198 enemies->enemies[idx].x = x;
199 enemies->enemies[idx].y = y;
200 enemies->enemies[idx].count = count;
201 enemies->enemies[idx].ticks = ticks;
202 enemies->spawned += count;
203 enemies->idx++;
204}
205
206
207void enemies_pop(struct enemies * enemies, int i) {
208 enemies->idx--;
209 memmove(&enemies->enemies[i], &enemies->enemies[i + 1],
210 (MAX_ENEMIES - (i + 1)) * sizeof(*enemies->enemies));
211}
212
213
214void turrets_push(
215 struct turrets * spawned_turrets, char grid[X][Y], int x, int y, int id
216) {
217 int idx = spawned_turrets->idx;
218 spawned_turrets->spawned[idx].x = x;
219 spawned_turrets->spawned[idx].y = y;
220 spawned_turrets->spawned[idx].id = id;
221 spawned_turrets->spawned[idx].radius = turrets[id].radius;
222 spawned_turrets->spawned[idx].rsplash = turrets[id].rsplash;
223 spawned_turrets->spawned[idx].damage = turrets[id].damage;
224 spawned_turrets->spawned[idx].dsplash = turrets[id].dsplash;
225 spawned_turrets->spawned[idx].stack = turrets[id].stack;
226 spawned_turrets->spawned[idx].ticks = turrets[id].ticks;
227 spawned_turrets->spawned[idx].level = 0;
228 spawned_turrets->spawned[idx].kills = 0;
229 spawned_turrets->idx++;
230
231 grid[x][y] |= CELL_TURRET | INT_TO_CELL_TURRET(id);
232}
233
234void turrets_pop(struct turrets * spawned, char grid[X][Y], int x, int y, int i) {
235 spawned->idx--;
236 memmove(&spawned->spawned[i], &spawned->spawned[i + 1],
237 (MAX_TURRETS - (i + 1)) * sizeof(*spawned->spawned));
238
239 grid[x][y] &= ~CELL_TURRET | INT_TO_CELL_TURRET(0xFF);
240}
241
242
243int rand_range(int lo, int hi) {
244 return rand() % (hi - lo) + lo;
245}
246
247
248void generate_path(char grid[X][Y], struct enemies * enemies) {
249 int spawnx = 0;
250 int spawny = rand() % Y;
251 enemies->spawnx = spawnx;
252 enemies->spawny = spawny;
253
254 int lastx = spawnx;
255 int lasty = spawny;
256 for (int i = 0; i < PATH_BENDS; i++) {
257 int bendx = rand_range(lastx + 1, (i + 1) * X / PATH_BENDS);
258 int bendy = rand_range(0, Y);
259
260 // horizontal run
261 for (int x = lastx; x < bendx; x++) {
262 grid[x][lasty] |= CELL_PATH | INT_TO_CELL_PATH(CELL_PATH_RIGHT);
263 }
264
265 // vertical connection
266 if (lasty != bendy) {
267 if (lasty < bendy) for (int y = lasty; y < bendy; y++) {
268 grid[bendx][y] |= CELL_PATH | INT_TO_CELL_PATH(CELL_PATH_DOWN);
269 } else for (int y = lasty; y > bendy; y--) {
270 grid[bendx][y] |= CELL_PATH | INT_TO_CELL_PATH(CELL_PATH_UP);
271 }
272 }
273
274 lastx = bendx;
275 lasty = bendy;
276 }
277
278 // connect to edge
279 for (int x = lastx; x < X; x++) {
280 grid[x][lasty] |= CELL_PATH | INT_TO_CELL_PATH(CELL_PATH_RIGHT);
281 }
282}
283
284
285char grid_getc(char grid[X][Y], int x, int y) {
286 char c = grid[x][y];
287 char out = ' ';
288 if (c & CELL_TURRET) out = turrets[CELL_TURRET_TO_INT(c)].symbol;
289 else if (c & CELL_PATH) switch(CELL_PATH_TO_INT(c)) {
290 case CELL_PATH_UP: out = '^'; break;
291 case CELL_PATH_DOWN: out = 'v'; break;
292 case CELL_PATH_LEFT: out = '<'; break;
293 case CELL_PATH_RIGHT: out = '>'; break;
294 }
295 return out;
296}
297
298
299int grid_getcolor(char grid[X][Y], int x, int y) {
300 char c = grid[x][y];
301 int out = 0;
302 if (c & CELL_TURRET) out = A_UNDERLINE;
303 else if (c & CELL_PATH) out = A_DIM;
304 return out;
305}
306
307
308void draw_grid(char grid[X][Y]) {
309 addch('+');
310 for (int x = 0; x < X; x++) addch('-');
311 addch('+');
312 addch('\n');
313 for (int y = 0; y < Y; y++) {
314 addch('|');
315 for (int x = 0; x < X; x++) {
316 int cp = grid_getcolor(grid, x, y);
317 attron(cp);
318 addch(grid_getc(grid, x, y));
319 attroff(cp);
320 }
321 addstr("|\n");
322 }
323 addch('+');
324 for (int x = 0; x < X; x++) addch('-');
325 addch('+');
326 addch('\n');
327}
328
329
330
331int get_shop_str(char * s, int n, int i) {
332 return snprintf(s, n, "%s: $%d", turrets[i].name,
333 turrets[i].cost);
334}
335
336
337int yx_to_shop_id(int y, int x) {
338 int id = Y_TO_SHOP_ID(y);
339 if (id < 0) return -1;
340 if (id >= ARRLEN(turrets)) return -1;
341
342 int len = get_shop_str(NULL, 0, id);
343 int off = x - SHOP_ID_TO_X(id);
344 if (off < 0 || off >= len) return -1;
345 return id;
346}
347
348
349void draw_shop(int cash) {
350 int y = SHOP_STARTY;
351 int x = SHOP_STARTX;
352 mvaddstr(y, x, "Shop:");
353 mvaddstr(++y, x, "-----");
354
355 char s[25];
356 for (int i = 0; i < ARRLEN(turrets); i++) {
357 x = SHOP_ID_TO_X(i);
358 y = SHOP_ID_TO_Y(i);
359 move(y, x);
360 int len = get_shop_str(s, ARRLEN(s), i);
361 if (cash >= turrets[i].cost) attron(A_REVERSE);
362 addstr(s);
363 if (cash >= turrets[i].cost) attroff(A_REVERSE);
364 }
365}
366
367
368void erase_shop(void) {
369 int y = SHOP_STARTY;
370 int x = SHOP_STARTX;
371 move(y, x);
372 clrtoeol();
373 move(++y, x);
374 clrtoeol();
375
376 for (int i = 0; i < ARRLEN(turrets); i++) {
377 x = SHOP_ID_TO_X(i);
378 y = SHOP_ID_TO_Y(i);
379 move(y, x);
380 clrtoeol();
381 }
382}
383
384
385void draw_radius(char grid[X][Y], int x, int y, int r) {
386 attron(A_REVERSE);
387 for (int rx = -r + 1; rx < r; rx++) {
388 for (int ry = -r + 1; ry < r; ry++) {
389 if (rx*rx + ry*ry > r*r) continue;
390 int dx = rx + x;
391 int dy = ry + y;
392 if (dx < 0) dx = 0;
393 else if (dx >= X) dx = X - 1;
394 if (dy < 0) dy = 0;
395 else if (dy >= Y) dy = Y - 1;
396 move(dy + 1, dx + 1);
397 addch(grid_getc(grid, dx, dy));
398 }
399 }
400 attroff(A_REVERSE);
401}
402
403
404int try_purchase(char grid[X][Y], int id, int cash, struct turrets * spawned_turrets) {
405 int cost = turrets[id].cost;
406 int radius = turrets[id].radius;
407 if (cost > cash) return -1;
408 timeout(-1);
409
410 bool done = false;
411 while (!done) {
412 move(Y + 2, X + 2 - 10);
413 addstr("q to abort");
414 move(Y + 3, X + 2 - 14);
415 addstr("Click to place");
416 refresh();
417
418 switch (getch()) {
419 case 'q':
420 cost = 0;
421 done = true;
422 break;
423 case KEY_MOUSE:;
424 MEVENT e;
425 if (getmouse(&e) != OK) break;
426 int x = e.x - 1;
427 int y = e.y - 1;
428 if (x < 0 || x >= X || y < 0 || y >= Y) break;
429 // turrets with a nonnegative stack must be placed on paths
430 // other turrets can only by placed on empty cells
431 if ((grid[x][y] & CELL_TURRET) ||
432 (turrets[id].stack < 0 && (grid[x][y] & CELL_PATH))) {
433 break;
434 }
435 if (turrets[id].stack > 0 && !(grid[x][y] & CELL_PATH)) break;
436
437 // draw radius
438 draw_radius(grid, x, y, radius);
439 move(e.y, e.x);
440 addch(turrets[id].symbol);
441 move(Y + 3, X + 2 - 15);
442 addstr("Enter to accept");
443 refresh();
444 if (getch() != '\n') {
445 cost = 0;
446 done = true;
447 break;
448 }
449
450 turrets_push(spawned_turrets, grid, x, y, id);
451 done = true;
452 break;
453 }
454 }
455
456 timeout(DELAY);
457 return cost;
458}
459
460
461int try_upgrade(int id, int cash, struct turrets * spawned_turrets, char grid[X][Y]) {
462 int cost = 0;
463 timeout(-1);
464
465 struct spawned_turret * st = &spawned_turrets->spawned[id];
466
467 erase_shop();
468 int y = SHOP_STARTY;
469 int x = SHOP_STARTX;
470 move(y, x);
471 addstr(turrets[st->id].name);
472 addch(':');
473 move(++y, x);
474 int nlen = strlen(turrets[st->id].name);
475 for (int i = 0; i < nlen + 1; i++) addch('-');
476
477 move(SHOP_ID_TO_Y(0), SHOP_ID_TO_X(0));
478 attron(A_REVERSE);
479 addstr("Turret Info");
480 attroff(A_REVERSE);
481
482 int tid = st->id;
483 int max_level = turrets[tid].n_upgrades;
484 int cur_level = st->level;
485 int up_idx = cur_level;
486 // sell for 75% of purchasing cost
487 int selling = 0;
488 selling += turrets[tid].cost;
489 for (int i = 0; i < cur_level; i++) {
490 selling += turrets[tid].upgrades[i].cost;
491 }
492 if (turrets[tid].stack > 0) {
493 selling *= st->stack;
494 selling /= turrets[tid].stack;
495 }
496 selling *= 3;
497 selling /= 4;
498 move(SHOP_ID_TO_Y(1), SHOP_ID_TO_X(1));
499 attron(A_REVERSE);
500 printw("Sell: -$%d", selling);
501 attroff(A_REVERSE);
502
503 if (cur_level < max_level) {
504 cost = turrets[tid].upgrades[up_idx].cost;
505 bool affordable = true;
506 if (cash < cost) affordable = false;
507 move(SHOP_ID_TO_Y(2), SHOP_ID_TO_X(2));
508 if (affordable) attron(A_REVERSE);
509 printw("Upgrade: $%d", cost);
510 if (affordable) attroff(A_REVERSE);
511
512
513 }
514
515 draw_radius(grid, st->x, st->y, st->radius);
516
517 bool done = false;
518 while (!done) {
519 move(Y + 2, X + 2 - 12);
520 addstr("Any to abort");
521 // clear the pause/resume prompt
522 move(Y + 3, X + 2 - 14);
523 clrtoeol();
524 refresh();
525
526 switch (getch()) {
527 case KEY_MOUSE:;
528 MEVENT e;
529 if (getmouse(&e) != OK) break;
530 int x = e.x - 1;
531 int y = e.y - 1;
532 int sid = yx_to_shop_id(e.y, e.x);
533 // turret info
534 if (sid == 0) {
535 int kills = st->kills;
536 int ticks = st->ticks;
537 int rad = st->radius;
538 int dmg = st->damage;
539 int rsp = st->rsplash;
540 int dsp = st->dsplash;
541 int stk = st->stack;
542 int py, px;
543 py = SHOP_STARTY;
544 px = SHOP_STARTX;
545 erase_shop();
546 move(py, px);
547 addstr(turrets[st->id].name);
548 addch(':');
549 move(++py, px);
550 for (int i = 0; i < nlen + 1; i++) addch('-');
551
552 mvprintw(++py, px, "Kills: %d", kills);
553 mvprintw(++py, px, "Level: %d", cur_level);
554 mvprintw(++py, px, "Attack Ticks: %d", ticks);
555 mvprintw(++py, px, "Radius: %d", rad);
556 mvprintw(++py, px, "Damage: %d", dmg);
557 if (rsp > 0 && dsp > 0) {
558 mvprintw(++py, px, "Splash Radius: %d", rsp);
559 mvprintw(++py, px, "Splash Damage: %d", dsp);
560 }
561 if (stk > 0) {
562 mvprintw(++py, px, "Stacked: %d", stk);
563 }
564
565 move(Y + 2, X + 2 - 13);
566 addstr("Any to finish");
567 refresh();
568 getch();
569 done = true;
570 cost = 0;
571 break;
572 // sell turret
573 } else if (sid == 1) {
574 cost = -selling;
575 done = true;
576 // upgrade turret
577 } else if (sid == 2) {
578 if (cash < cost) break;
579 st->level++;
580 st->radius = turrets[tid].upgrades[up_idx].radius;
581 st->rsplash = turrets[tid].upgrades[up_idx].rsplash;
582 st->damage = turrets[tid].upgrades[up_idx].damage;
583 st->dsplash = turrets[tid].upgrades[up_idx].dsplash;
584 st->ticks = turrets[tid].upgrades[up_idx].ticks;
585 done = true;
586 break;
587 }
588 break;
589 default:
590 cost = 0;
591 done = true;
592 break;
593 }
594 }
595
596 timeout(DELAY);
597 return cost;
598}
599
600
601void draw_enemies(struct enemies * enemies) {
602 for (int i = 0; i < enemies->idx; i++) {
603 if (enemies->enemies[i].count == 0) continue;
604 int cp = 0;
605 switch (enemies->enemies[i].count) {
606 case 1: cp = COLOR_PAIR(RED); break;
607 case 2: cp = COLOR_PAIR(CYAN); break;
608 case 3: cp = COLOR_PAIR(GREEN); break;
609 case 4: cp = COLOR_PAIR(YELLOW); break;
610 default: cp = COLOR_PAIR(MAGENTA); break;
611 }
612 int x = enemies->enemies[i].x;
613 int y = enemies->enemies[i].y;
614 move(1 + y, 1 + x);
615 attron(cp);
616 addch('@');
617 attroff(cp);
618 }
619}
620
621
622int get_spawn_rate(int round) {
623 if (round <= 10) return 5;
624 if (round <= 35) return rand_range(2,4);
625 return rand_range(1, 4);
626}
627
628
629int get_speed(int round) {
630 if (round <= 10) return 5;
631 if (round <= 35) return rand_range(3, 5);
632 return rand_range(1, 3);
633}
634
635
636int get_stack(int round) {
637 int lower_rounds[] = {
638 1, 1, 1, 2, 2,
639 };
640 int mid_rounds[] = {
641 1, 1, 1, 1, 1, 1, 1, 1, 1,
642 2, 2, 2, 2, 2, 2, 2, 2, 2,
643 2, 2, 2, 2, 2, 3, 3, 3, 3,
644 3, 3, 3, 3, 3, 3, 4, 4, 4,
645 };
646 int mid_rounds2[] = {
647 2, 2, 2, 3, 3, 3, 3, 3, 3,
648 4, 4, 4, 4, 4, 4, 4, 5, 5,
649 5, 5, 6, 6, 6, 7, 7, 8, 9
650 };
651 if (round <= 10) return lower_rounds[rand_range(0, ARRLEN(lower_rounds))];
652 if (round <= 35) return mid_rounds[rand_range(0, ARRLEN(mid_rounds))];
653 if (round <= 60) return mid_rounds2[rand_range(0, ARRLEN(mid_rounds2))];
654 return rand_range(5, 20);
655}
656
657
658int get_to_spawn(int round) {
659 int ret = 0;
660 if (round <= 3) ret = (round + 1) * 4 + rand_range(0, 2);
661 else if (round <= 20) ret = round * 3 + rand_range(0, 5);
662 else if (round <= 40) ret = 2 * MAX_ENEMIES / 3 + rand_range(-5, 5);
663 else ret = 7 * MAX_ENEMIES / 8 + rand_range(-5, 5);
664 return ret > MAX_ENEMIES - 20 ? MAX_ENEMIES - 20 : ret;
665}
666
667
668int spawn_enemies(struct enemies * enemies, char grid[X][Y], int round) {
669 if (enemies->last_round != round) {
670 enemies->spawned = 0;
671 enemies->killed = 0;
672 enemies->last_round = round;
673 enemies->to_spawn = get_to_spawn(round);
674 }
675
676 int deaths = 0;
677
678 // advance all enemies
679 // iterate backward so that popping doesn't mess up the iteration
680 for (int i = enemies->idx - 1; i >= 0; i--) {
681 if (enemies->enemies[i].count == 0) continue;
682 if (ticks % enemies->enemies[i].ticks != 0) continue;
683 int x = enemies->enemies[i].x;
684 int y = enemies->enemies[i].y;
685 int c = grid[x][y];
686 if (c & CELL_PATH) switch (CELL_PATH_TO_INT(c)) {
687 case CELL_PATH_UP: y--; break;
688 case CELL_PATH_DOWN: y++; break;
689 case CELL_PATH_LEFT: x--; break;
690 case CELL_PATH_RIGHT: x++; break;
691 }
692 enemies->enemies[i].x = x;
693 enemies->enemies[i].y = y;
694 if (x >= X || y >= Y) {
695 enemies->killed += enemies->enemies[i].count;
696 deaths += enemies->enemies[i].count;
697 enemies_pop(enemies, i);
698 }
699 }
700
701 if (enemies->spawned >= enemies->to_spawn) return deaths;
702
703 int spawn_rate = get_spawn_rate(round);
704 int speed = get_speed(round);
705 int stack = get_stack(round);
706 if (ticks % spawn_rate == 0) {
707 enemies_push(enemies, enemies->spawnx, enemies->spawny, stack, speed);
708 }
709
710 return deaths;
711}
712
713
714int find_nearest_enemy(struct enemies * enemies, int x, int y, int rad) {
715 if (enemies->idx == 0) return -1;
716
717 int nearestx = X + 1;
718 int nearesty = Y + 1;
719 int nearestid = -1;
720
721 for (int i = 0; i < enemies->idx; i++) {
722 int ex = enemies->enemies[i].x - x;
723 int ey = enemies->enemies[i].y - y;
724 if (ex*ex + ey*ey > rad*rad) continue;
725 if (ex*ex + ey*ey < nearestx*nearestx + nearesty*nearesty) {
726 nearestx = ex;
727 nearesty = ey;
728 nearestid = i;
729 }
730 }
731
732 return nearestid;
733}
734
735
736int attack_enemy(struct enemies * enemies, int id, int dmg) {
737 int kills = enemies->enemies[id].count;
738 enemies->enemies[id].count -= dmg;
739 if (dmg >= kills) {
740 enemies_pop(enemies, id);
741 } else kills = dmg;
742 enemies->killed += kills;
743 return kills;
744}
745
746
747int splash_enemies(struct enemies * enemies, int x, int y, int rad, int dmg) {
748 int kills = 0;
749 for (int i = 0; i < enemies->idx; i++) {
750 int ex = enemies->enemies[i].x - x;
751 int ey = enemies->enemies[i].y - y;
752 if (ex*ex + ey*ey > rad*rad) continue;
753
754 kills += attack_enemy(enemies, i, dmg);
755 }
756
757 return kills;
758}
759
760
761int run_turrets(struct turrets * spawned, struct enemies * enemies, char grid[X][Y]) {
762 int kills = 0;
763
764 for (int i = 0; i < spawned->idx; i++) {
765 int tid = spawned->spawned[i].id;
766 int tx = spawned->spawned[i].x;
767 int ty = spawned->spawned[i].y;
768 int radius = spawned->spawned[i].radius;
769 int damage = spawned->spawned[i].damage;
770 int rsplash = spawned->spawned[i].rsplash;
771 int dsplash = spawned->spawned[i].dsplash;
772
773 if (ticks % spawned->spawned[i].ticks != 0) continue;
774
775 int nearest = find_nearest_enemy(enemies, tx, ty, radius);
776 if (nearest < 0) continue;
777
778 int nx = enemies->enemies[nearest].x;
779 int ny = enemies->enemies[nearest].y;
780
781 // damage animation
782 move(ny + 1, nx + 1);
783 attron(A_REVERSE);
784 addch(grid_getc(grid, nx, ny));
785 refresh();
786 napms(ATTACK_ANIMATION_DELAY);
787 attroff(A_REVERSE);
788 refresh();
789
790 int just_killed = attack_enemy(enemies, nearest, damage);
791 if (rsplash > 0 && dsplash > 0)
792 just_killed += splash_enemies(enemies, nx, ny, rsplash, dsplash);
793
794 kills += just_killed;
795 if (spawned->spawned[i].stack > 0) {
796 spawned->spawned[i].stack -= just_killed;
797 if (spawned->spawned[i].stack <= 0) {
798 turrets_pop(spawned, grid, tx, ty, i);
799 }
800 }
801
802 spawned->spawned[i].kills += just_killed;
803 }
804
805 return kills;
806}
807
808
809bool no_enemies(struct enemies * enemies) {
810 return enemies->killed >= enemies->to_spawn;
811}
812
813
814int main(void) {
815 srand(SEED);
816
817 initscr();
818 noecho();
819 curs_set(0);
820 keypad(stdscr, TRUE);
821 timeout(DELAY);
822
823 mousemask(BUTTON1_CLICKED, NULL);
824
825 use_default_colors();
826 start_color();
827 init_pair(RED, COLOR_RED, -1);
828 init_pair(GREEN, COLOR_GREEN, -1);
829 init_pair(YELLOW, COLOR_YELLOW, -1);
830 init_pair(BLUE, COLOR_BLUE, -1);
831 init_pair(MAGENTA, COLOR_MAGENTA, -1);
832 init_pair(CYAN, COLOR_CYAN, -1);
833
834 while (true) {
835 char grid[X][Y] = {};
836 struct enemies enemies = {};
837 struct turrets spawned_turrets = {};
838
839 int cash = STARTING_CASH;
840 int lives = STARTING_LIVES;
841 int round = STARTING_ROUND;
842 int score = 0;
843
844 generate_path(grid, &enemies);
845
846 bool paused = true;
847 int done = RUNNING;
848 while (!done) {
849 if (!paused) {
850 int deaths = spawn_enemies(&enemies, grid, round);
851 lives -= deaths;
852
853 if (no_enemies(&enemies)) {
854 round++;
855 }
856
857 if (lives < 0) {
858 done = GAME_OVER;
859 }
860
861 int killed = run_turrets(&spawned_turrets, &enemies, grid);
862 if (killed >= 0) {
863 cash += killed;
864 score += killed;
865 }
866 }
867
868 erase();
869 draw_grid(grid);
870 draw_enemies(&enemies);
871 draw_shop(cash);
872 move(Y + 2, 0);
873 printw("Round: %d\n", round);
874 printw("Lives: %d\n", lives);
875 printw("Cash: %d\n", cash);
876 printw("Score: %d\n", score);
877 move(Y + 2, X + 2 - 9);
878 addstr("q to quit");
879 move(Y + 3, X + 2 - 14);
880 if (paused) addstr(" Any to resume");
881 else addstr("Space to pause");
882 refresh();
883
884 switch (getch()) {
885 case 'q': done = QUIT; break;
886 case ' ':
887 paused = !paused;
888 break;
889 case KEY_MOUSE:;
890 MEVENT e;
891 if (getmouse(&e) != OK) break;
892
893 int id = yx_to_shop_id(e.y, e.x);
894 if (id >= 0) {
895 int cost = try_purchase(grid, id, cash, &spawned_turrets);
896 if (cost < 0) {
897 move(Y/2 + 1, X/2 + 1 - 9);
898 attron(A_REVERSE);
899 addstr("Insufficient Funds");
900 attroff(A_REVERSE);
901 refresh();
902 napms(500);
903 } else {
904 cash -= cost;
905 }
906 } else {
907 int tid = -1;
908 int x, y;
909 for (int i = 0; i < spawned_turrets.idx; i++) {
910 x = spawned_turrets.spawned[i].x;
911 y = spawned_turrets.spawned[i].y;
912 if (x != e.x - 1 || y != e.y - 1) continue;
913
914 tid = i;
915 break;
916 }
917
918 if (tid >= 0) {
919 int cost = try_upgrade(tid, cash, &spawned_turrets, grid);
920 cash -= cost;
921 // turret was sold, remove it
922 if (cost < 0) {
923 turrets_pop(&spawned_turrets, grid, x, y, tid);
924 }
925 }
926 }
927 break;
928 case ERR: break;
929 default:
930 if (paused) paused = false;
931 break;
932 }
933
934 ticks++;
935 }
936
937 switch (done) {
938 case QUIT: goto terminate;
939 case GAME_OVER:
940 move(Y/2 + 1, X/2 + 1 - 12);
941 addch(' ');
942 attron(A_REVERSE);
943 addstr("You ran out of lives :(");
944 attroff(A_REVERSE);
945 addch(' ');
946 move(Y/2 + 2, X/2 + 1 - 12);
947 addch(' ');
948 attron(A_REVERSE);
949 printw("Final Score: %9d ", score);
950 attroff(A_REVERSE);
951 addch(' ');
952 break;
953 }
954
955 move(Y/2 + 3, X/2 + 1 - 14);
956 addch(' ');
957 attron(A_REVERSE);
958 addstr("Any to play again, q to quit");
959 attroff(A_REVERSE);
960 addch(' ');
961 timeout(-1);
962 if (getch() == 'q') goto terminate;
963 timeout(DELAY);
964 }
965
966 terminate:
967 keypad(stdscr, FALSE);
968 curs_set(1);
969 echo();
970 endwin();
971}
972