globox.c (15168B)
1 /* See LICENSE file for copyright and license details. 2 * 3 * globox is a platform game for the terminal. 4 * 5 * Each symbol displayed on the screen is called a block, including players. 6 * Blocks are organized in a linked block list which is filled with the objects 7 * found on the level. 8 * 9 * Keys and objects are organized as arrays and defined in config.h. 10 * 11 * To understand everything else, start reading main(). 12 */ 13 14 #define _BSD_SOURCE 15 #include <errno.h> 16 #include <limits.h> 17 #include <locale.h> 18 #include <signal.h> 19 #include <stdarg.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <sys/ioctl.h> 24 #include <termios.h> 25 #include <time.h> 26 #include <unistd.h> 27 28 #include "arg.h" 29 char *argv0; 30 31 /* macros */ 32 #define LENGTH(X) (sizeof X / sizeof X[0]) 33 #define DELAY(B, K, D) (++(B)->delays[(K)] < (D) ? 1 : ((B)->delays[(K)] = 0)) 34 #define ISSET(F, B) ((F) & (B)) 35 #define TICK 64000 36 #define VACUUM ' ' 37 #define LINESEP '\n' 38 39 /* VT100 escape sequences */ 40 #define CLEAR "\33[2J" 41 #define CLEARLN "\33[2K" 42 #define CLEARRIGHT "\33[0K" 43 #define CURPOS "\33[%d;%dH" 44 #define CURSON "\33[?25h" 45 #define CURSOFF "\33[?25l" 46 47 #if defined CTRL && defined _AIX 48 #undef CTRL 49 #endif 50 #ifndef CTRL 51 #define CTRL(k) ((k) & 0x1F) 52 #endif 53 #define CTRL_ALT(k) ((k) + (129 - 'a')) 54 55 /* object flags */ 56 #define OF_OPENUP 1<<1 57 #define OF_OPENRIGHT 1<<2 58 #define OF_OPENDOWN 1<<3 59 #define OF_OPENLEFT 1<<4 60 #define OF_JUMPFROM 1<<5 61 #define OF_PLAYER 1<<6 62 #define OF_STICK 1<<7 63 #define OF_FALL 1<<8 64 #define OF_PUSHABLE 1<<9 65 #define OF_AI 1<<10 66 #define OF_OPEN (OF_OPENUP|OF_OPENRIGHT|OF_OPENDOWN|OF_OPENLEFT) 67 68 /* enums */ 69 enum { KeyUp = -50, KeyDown, KeyRight, KeyLeft, KeyHome, KeyEnd, KeyDel, KeyPgUp, KeyPgDw }; 70 enum { DelayCannon, DelayCannonBall, DelayFalling, DelayZombie, DelayMax }; 71 72 typedef union { 73 int i; 74 unsigned int ui; 75 float f; 76 const void *v; 77 } Arg; 78 79 typedef struct { 80 char *name; 81 char *map; 82 } Level; 83 84 typedef struct Object Object; 85 struct Object { 86 char sym; 87 int zidx; 88 unsigned int flags; 89 int (*ontick)(Object *); 90 const Arg arg; 91 }; 92 93 typedef struct { 94 const int key; 95 void (*func)(const Arg *); 96 const Arg arg; 97 } Key; 98 99 typedef struct Block Block; 100 struct Block { 101 Object *o; 102 int delays[DelayMax]; 103 int x, y; 104 int energy; 105 int _draw; /* used to optimize draw() */ 106 Block *next; 107 }; 108 109 typedef struct { 110 Block *blocks; 111 int w, h; 112 } Scene; 113 114 /* function declarations */ 115 void attach(Block *b); 116 int cannon(Object *o); 117 int cannonball(Object *o); 118 void checkgame(void); 119 char choose(char *opts, const char *msgstr, ...); 120 void cleanup(void); 121 void detach(Block *b); 122 void die(const char *errstr, ...); 123 void draw(void); 124 void drawbar(void); 125 int earnenergy(Object *o); 126 void *ecalloc(size_t nmemb, size_t size); 127 int falling(Object *o); 128 int finish(Object *o); 129 void flow(void); 130 void freescene(void); 131 int getkey(void); 132 void ioblock(int block); 133 void jump(const Arg *arg); 134 void keypress(void); 135 void level(int lev); 136 int mvprintf(int x, int y, char *fmt, ...); 137 Object *objbysym(char sym); 138 void objwalk(Object *o, int offset); 139 void quit(const Arg *arg); 140 int readchar(void); 141 void resize(int x, int y); 142 void restart(const Arg *arg); 143 void run(void); 144 void setup(void); 145 void sigwinch(int unused); 146 int sleepu(double usec); 147 void usage(void); 148 void walk(Block *blk, int offset); 149 void walkleft(const Arg *arg); 150 void walkright(const Arg *arg); 151 int zombie(Object *o); 152 153 /* variables */ 154 Scene *scene; 155 struct termios origti; 156 int running = 1, lev; 157 int rows, cols; 158 159 /* configuration, allows nested code to access above variables */ 160 #include "config.h" 161 162 /* function implementations */ 163 void 164 attach(Block *b) { 165 b->next = scene->blocks; 166 scene->blocks = b; 167 } 168 169 int 170 cannon(Object *o) { 171 Block *c, *nb; 172 173 for(c = scene->blocks; c; c = c->next) { 174 if(c->o->ontick != cannon || DELAY(c, DelayCannon, 128)) 175 continue; 176 nb = ecalloc(1, sizeof(Block)); 177 nb->o = ((Object *)(c->o->arg.v)); 178 nb->x = c->x; 179 nb->y = c->y; 180 attach(nb); 181 } 182 return 0; 183 } 184 185 int 186 cannonball(Object *o) { 187 Block *cb, *b, *p = NULL, *rm = NULL; 188 int out; 189 190 for(cb = scene->blocks; cb; cb = cb->next) { 191 if(cb->o->ontick != cannonball || DELAY(cb, DelayCannonBall, 2)) 192 continue; 193 cb->x += cb->o->arg.i; 194 out = 1; 195 for(b = scene->blocks; b; b = b->next) { 196 if(cb != b && b->x == cb->x && b->y == cb->y) { 197 out = 0; 198 if(!p && ISSET(b->o->flags, OF_PLAYER)) { 199 p = b; 200 continue; 201 } 202 if(!ISSET(b->o->flags, cb->o->arg.i > 0 ? OF_OPENLEFT : OF_OPENRIGHT)) { 203 rm = cb; 204 break; 205 } 206 } 207 } 208 if(out) 209 rm = cb; 210 if(p) { 211 p->energy -= 2; 212 p = NULL; 213 } 214 if(rm) { 215 cb = cb->next; 216 detach(rm); 217 free(rm); 218 rm = NULL; 219 } 220 } 221 return 0; 222 } 223 224 void 225 checkgame(void) { 226 Block *fp = NULL, *p; 227 int np = 0; 228 229 for(p = scene->blocks; p;) { 230 if(!ISSET(p->o->flags, OF_PLAYER)) { 231 p = p->next; 232 continue; 233 } 234 if(p->energy > 0) { 235 if(!ISSET(p->o->flags, OF_AI)) 236 ++np; 237 p = p->next; 238 } 239 else { 240 fp = p; 241 p = p->next; 242 detach(fp); 243 free(fp); 244 } 245 } 246 if(np) 247 return; 248 if(choose("yn", "Level failed. Play again ([y]/n)?") == 'y') 249 level(lev); 250 else 251 running = 0; 252 } 253 254 char 255 choose(char *opts, const char *msgstr, ...) { 256 va_list ap; 257 int c; 258 char *o = NULL; 259 260 va_start(ap, msgstr); 261 fprintf(stdout, CURPOS, cols - 1, 0); 262 vfprintf(stdout, msgstr, ap); 263 va_end(ap); 264 fflush(stdout); 265 266 ioblock(1); 267 while(!(o && *o) && (c = getkey()) != EOF) { 268 if(c == '\n') 269 o = &opts[0]; 270 else 271 for(o = opts; *o; ++o) 272 if(c == *o) 273 break; 274 } 275 fprintf(stdout, CURPOS CLEARLN, cols - 1, 0); 276 ioblock(0); 277 return *(o ? o : opts); 278 } 279 280 void 281 cleanup(void) { 282 freescene(); 283 tcsetattr(0, TCSANOW, &origti); 284 printf(CURSON); 285 } 286 287 void 288 detach(Block *b) { 289 Block **tb; 290 291 for (tb = &scene->blocks; *tb && *tb != b; tb = &(*tb)->next); 292 *tb = b->next; 293 } 294 295 void 296 die(const char *errstr, ...) { 297 va_list ap; 298 299 if(rows) 300 cleanup(); 301 va_start(ap, errstr); 302 vfprintf(stderr, errstr, ap); 303 va_end(ap); 304 exit(1); 305 } 306 307 void 308 draw(void) { 309 Block *b, *b2, *t; 310 311 for(b = scene->blocks; b; b = b->next) 312 b->_draw = 1; 313 for(b = scene->blocks; b; b = b->next) { 314 if(!b->_draw) 315 continue; 316 t = b; 317 for(b2 = scene->blocks; b2; b2 = b2->next) 318 if(b2 != b && b2->x == b->x && b2->y == b->y 319 && b2->o->zidx > t->o->zidx) 320 t = b2; 321 for(b2 = scene->blocks; b2; b2 = b2->next) 322 if(b2 != t && t->x == b2->x && t->y == b2->y) 323 b2->_draw = 0; 324 } 325 for(b = scene->blocks; b; b = b->next) 326 if(b->_draw) 327 mvprintf(b->x, b->y + 1, "%c", b->o->sym); 328 drawbar(); 329 } 330 331 void 332 drawbar(void) { 333 Block *p; 334 int i, len; 335 336 len = mvprintf(1, 1, "[%d|%dx%d] %s ::", lev, scene->w, scene->h, levels[lev].name); 337 for(p = scene->blocks; p; p = p->next) { 338 if(!(p->o->flags & OF_PLAYER)) 339 continue; 340 i = mvprintf(len+1, 1, " %c(*%d)(%dx%d)", 341 p->o->sym, p->energy, p->x, p->y); 342 len += i; 343 } 344 mvprintf(len+1, 1, "%s", CLEARRIGHT); 345 } 346 347 int 348 earnenergy(Object *o) { 349 Block *b, *p; 350 351 for(b = scene->blocks; b; b = b->next) { 352 if(b->o != o) 353 continue; 354 for(p = scene->blocks; p; p = p->next) 355 if(ISSET(p->o->flags, OF_PLAYER) && p->x == b->x && p->y == b->y) 356 break; 357 if(!p) 358 continue; 359 b->o = objbysym(VACUUM); 360 p->energy += o->arg.i; 361 break; 362 } 363 return 0; 364 } 365 366 void * 367 ecalloc(size_t nmemb, size_t size) { 368 void *p; 369 370 if(!(p = calloc(nmemb, size))) 371 die("Cannot allocate memory.\n"); 372 return p; 373 } 374 375 int 376 falling(Object *o) { 377 Block *b, *p; 378 int ny, valid, skip; 379 380 for(p = scene->blocks; p; p = p->next) { 381 if(!ISSET(p->o->flags, OF_FALL) || DELAY(p, DelayFalling, 4)) 382 continue; 383 valid = 0; 384 skip = 0; 385 ny = p->y + 1; 386 for(b = scene->blocks; b; b = b->next) { 387 if(b == p || b->x != p->x) 388 continue; 389 if(b->y == p->y) { 390 if(ISSET(b->o->flags, OF_STICK)) 391 skip = 1; 392 } 393 else if(b->y == ny) { 394 valid = 1; 395 if(!ISSET(b->o->flags, OF_OPENUP)) 396 skip = 1; 397 } 398 } 399 if(skip) 400 continue; 401 if(!valid) { 402 p->energy = 0; 403 continue; 404 } 405 p->y = ny; 406 } 407 return 0; 408 } 409 410 int 411 finish(Object *o) { 412 Block *b, *p; 413 int np = 0; 414 415 for(p = scene->blocks; p; p = p->next) { 416 if(!ISSET(p->o->flags, OF_PLAYER) || p->energy <= 0 || ISSET(p->o->flags, OF_AI)) 417 continue; 418 ++np; 419 for(b = scene->blocks; b; b = b->next) 420 if(b->o->ontick == finish && b->x == p->x && b->y == p->y) 421 break; 422 if(!b) 423 return 0; 424 } 425 if(!np) 426 return 0; 427 if(++lev >= LENGTH(levels)) { 428 if(choose("yn", "The game has finished. Play again ([y]/n)?") == 'n') { 429 running = 0; 430 return 1; 431 } 432 lev = 0; 433 } 434 else if(choose("yn", "Level completed. Play next ([y]/n)?") == 'n') { 435 running = 0; 436 return 1; 437 } 438 level(lev); 439 return 1; 440 } 441 442 void 443 flow(void) { 444 int i; 445 446 for(i = 0; i < LENGTH(objects); ++i) 447 if(objects[i].ontick && objects[i].ontick(&objects[i])) 448 return; 449 } 450 451 void 452 freescene(void) { 453 Block *b; 454 455 if(!scene) 456 return; 457 while(scene->blocks) { 458 b = scene->blocks; 459 scene->blocks = scene->blocks->next; 460 free(b); 461 } 462 free(scene); 463 } 464 465 /* XXX quick'n dirty implementation */ 466 int 467 getkey(void) { 468 int key = readchar(), c; 469 470 if(key != '\x1b' || readchar() != '[') 471 return key; 472 switch((c = readchar())) { 473 case 'A': key = KeyUp; break; 474 case 'B': key = KeyDown; break; 475 case 'C': key = KeyRight; break; 476 case 'D': key = KeyLeft; break; 477 case 'H': key = KeyHome; break; 478 case 'F': key = KeyEnd; break; 479 case '1': key = KeyHome; break; 480 case '3': key = KeyDel; break; 481 case '4': key = KeyEnd; break; 482 case '5': key = KeyPgUp; break; 483 case '6': key = KeyPgDw; break; 484 case '7': key = KeyHome; break; 485 case '8': key = KeyEnd; break; 486 default: 487 /* debug */ 488 mvprintf(1, rows, "Unknown char: %c (%d)", c, c); 489 break; 490 } 491 return key; 492 } 493 494 void 495 ioblock(int block) { 496 struct termios ti; 497 498 tcgetattr(0, &ti); 499 ti.c_cc[VMIN] = block; 500 tcsetattr(0, TCSANOW, &ti); 501 } 502 503 void 504 jump(const Arg *arg) { 505 Block *b, *p; 506 int up, down, canjump, upclose, dwclose; 507 508 for(p = scene->blocks; p; p = p->next) { 509 if(p->o != arg->v) 510 continue; 511 up = p->y - 1; 512 down = p->y + 1; 513 canjump = 0; 514 dwclose = 0; 515 upclose = -1; 516 for(b = scene->blocks; b; b = b->next) { 517 if(b->x != p->x) 518 continue; 519 if(b->y == p->y) { 520 if(ISSET(b->o->flags, OF_JUMPFROM)) 521 canjump = 1; 522 } 523 else if(b->y == up) { 524 if(!ISSET(b->o->flags, OF_OPENDOWN)) 525 upclose = 1; 526 else if(upclose == -1) 527 upclose = 0; 528 } 529 else if(b->y == down) { 530 if(!ISSET(b->o->flags, OF_OPENUP)) 531 dwclose = 1; 532 } 533 } 534 if(upclose || !(canjump || dwclose)) 535 continue; 536 p->y = up; 537 /* reset delays */ 538 for(up = 0; up < DelayMax; ++up) 539 p->delays[up] = 0; 540 } 541 } 542 543 void 544 keypress(void) { 545 int key = getkey(), i; 546 547 for(i = 0; i < LENGTH(keys); ++i) 548 if(keys[i].key == key) 549 keys[i].func(&keys[i].arg); 550 while(getkey() != EOF); /* discard remaining input */ 551 } 552 553 void 554 level(int num) { 555 Object *o; 556 Block *b; 557 int i, len, x, y; 558 char *map; 559 560 if(num >= LENGTH(levels)) 561 die("%s: level %d does not exists.\n", argv0, num); 562 freescene(); 563 scene = ecalloc(1, sizeof(Scene)); 564 map = levels[num].map; 565 len = strlen(map); 566 x = y = 1; 567 for(i = 0; i < len; ++i) { 568 if(map[i] == LINESEP) { 569 ++y; 570 if(x > scene->w) 571 scene->w = x; 572 x = 1; 573 continue; 574 } 575 o = objbysym(map[i]); 576 if(!o) 577 die("%s: unknown object '%c' at %dx%d.\n", argv0, map[i], x, y); 578 b = ecalloc(1, sizeof(Block)); 579 b->o = o; 580 b->x = x; 581 b->y = y; 582 attach(b); 583 if(ISSET(o->flags, OF_PLAYER | OF_FALL | OF_AI)) { 584 o = objbysym(VACUUM); 585 if(o) { 586 b = ecalloc(1, sizeof(Block)); 587 b->o = o; 588 b->x = x; 589 b->y = y; 590 attach(b); 591 } 592 } 593 ++x; 594 } 595 scene->h = y + 1; 596 /* revive the zombies players */ 597 for(b = scene->blocks; b; b = b->next) 598 if(ISSET(b->o->flags, OF_PLAYER) && !b->energy) 599 b->energy = b->o->arg.i; 600 printf(CLEAR); 601 } 602 603 int 604 mvprintf(int x, int y, char *fmt, ...) { 605 va_list ap; 606 int len; 607 608 printf(CURPOS, y, x); 609 va_start(ap, fmt); 610 len = vfprintf(stdout, fmt, ap); 611 va_end(ap); 612 return len; 613 } 614 615 Object * 616 objbysym(char sym) { 617 int i; 618 619 for(i = 0; i < LENGTH(objects); ++i) 620 if(objects[i].sym == sym) 621 return &objects[i]; 622 return NULL; 623 } 624 625 void 626 objwalk(Object *o, int offset) { 627 Block *b; 628 629 for(b = scene->blocks; b; b = b->next) 630 if(b->o == o) 631 walk(b, offset); 632 } 633 634 void 635 quit(const Arg *arg) { 636 if(!arg->i || choose("ny", "Are you sure (y/[n])?") == 'y') 637 running = 0; 638 } 639 640 int 641 readchar(void) { 642 char buf[1] = {0}; 643 return (read(0, buf, 1) < 1 ? EOF : buf[0]); 644 } 645 646 void 647 resize(int x, int y) { 648 rows = x; 649 cols = y; 650 } 651 652 void 653 restart(const Arg *arg) { 654 if(choose("ny", "Restart the level (y/[n])?") == 'y') 655 level(lev); 656 } 657 658 void 659 run(void) { 660 ioblock(0); 661 while(running) { 662 while(cols < scene->w || rows < scene->h) { 663 mvprintf(1, 1, "Terminal too small."); 664 sleepu(10000); 665 } 666 draw(); 667 checkgame(); 668 keypress(); 669 flow(); 670 if(sleepu(TICK)) 671 die("%s: error while sleeping\n", argv0); 672 } 673 ioblock(1); 674 mvprintf(1, cols, "%s", CLEARLN); 675 } 676 677 void 678 setup(void) { 679 struct termios ti; 680 struct sigaction sa; 681 struct winsize ws; 682 683 setlocale(LC_CTYPE, ""); 684 sa.sa_flags = 0; 685 sigemptyset(&sa.sa_mask); 686 sa.sa_handler = sigwinch; 687 sigaction(SIGWINCH, &sa, NULL); 688 tcgetattr(0, &origti); 689 cfmakeraw(&ti); 690 ti.c_iflag |= ICRNL; 691 ti.c_cc[VMIN] = 0; 692 ti.c_cc[VTIME] = 0; 693 tcsetattr(0, TCSAFLUSH, &ti); 694 printf(CURSOFF); 695 ioctl(0, TIOCGWINSZ, &ws); 696 resize(ws.ws_row, ws.ws_col); 697 } 698 699 void 700 sigwinch(int unused) { 701 struct winsize ws; 702 703 ioctl(0, TIOCGWINSZ, &ws); 704 resize(ws.ws_row, ws.ws_col); 705 printf(CLEAR); 706 } 707 708 int 709 sleepu(double usec) { 710 struct timespec req, rem; 711 int r; 712 713 req.tv_sec = 0; 714 req.tv_nsec = usec * 1000; 715 while((r = nanosleep(&req, &rem)) == -1 && errno == EINTR) 716 req = rem; 717 return r; 718 } 719 720 void 721 usage(void) { 722 die("Usage: %s [-v] [-n <level>]\n", argv0); 723 } 724 725 void 726 walk(Block *blk, int offset) { 727 Block *b; 728 int nx, nblk; 729 730 nx = blk->x + offset; 731 nblk = 0; 732 for(b = scene->blocks; b; b = b->next) { 733 if(b->x != nx || b->y != blk->y) 734 continue; 735 ++nblk; 736 if(!ISSET(b->o->flags, offset > 0 ? OF_OPENLEFT : OF_OPENRIGHT)) { 737 if(ISSET(b->o->flags, OF_PUSHABLE)) { 738 walk(b, offset); 739 if(b->x != nx) 740 continue; 741 } 742 return; 743 } 744 } 745 if(!nblk) 746 return; 747 blk->x = nx; 748 } 749 750 void 751 walkleft(const Arg *arg) { 752 objwalk((Object *)arg->v, -1); 753 } 754 755 void 756 walkright(const Arg *arg) { 757 objwalk((Object *)arg->v, +1); 758 } 759 760 int 761 zombie(Object *o) { 762 Arg arg; 763 Block *b, *p; 764 int t; 765 766 for(b = scene->blocks; b; b = b->next) { 767 if(b->o->ontick != zombie) 768 continue; 769 /* collide */ 770 for(p = scene->blocks; p; p = p->next) 771 if(p->o->ontick != zombie && ISSET(p->o->flags, OF_PLAYER) 772 && p->x == b->x && p->y == b->y) 773 --p->energy; 774 /* walk (maybe) */ 775 if(DELAY(b, DelayZombie, 4)) 776 continue; 777 /* detect near cannon balls */ 778 for(p = scene->blocks; p; p = p->next) 779 if(p->o->ontick == cannonball && p->y == b->y 780 && p->x >= b->x - 3 && p->x <= b->x + 3) 781 break; 782 if(p) { 783 arg.v = b->o; 784 jump(&arg); 785 } 786 t = rand() % 3; 787 if(t == 2) 788 t = -1; 789 if(t) 790 walk(b, t); 791 } 792 return 0; 793 } 794 795 int 796 main(int argc, char *argv[]) { 797 ARGBEGIN { 798 case 'n': lev = atoi(EARGF(usage())); break; 799 case 'v': die("globox-"VERSION"\n"); 800 default: usage(); 801 } ARGEND; 802 srand(time(NULL)); 803 setup(); 804 level(lev); 805 run(); 806 cleanup(); 807 return 0; 808 }