globox

Platform game for the terminal
git clone git://git.bitsmanent.org/globox
Log | Files | Refs | README | LICENSE

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 }