Home | History | Annotate | Line # | Download | only in worms
worms.c revision 1.27
      1 /*	$NetBSD: worms.c,v 1.27 2023/04/18 15:02:22 kre Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1980, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the University nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 #ifndef lint
     34 __COPYRIGHT("@(#) Copyright (c) 1980, 1993\
     35  The Regents of the University of California.  All rights reserved.");
     36 #endif /* not lint */
     37 
     38 #ifndef lint
     39 #if 0
     40 static char sccsid[] = "@(#)worms.c	8.1 (Berkeley) 5/31/93";
     41 #else
     42 __RCSID("$NetBSD: worms.c,v 1.27 2023/04/18 15:02:22 kre Exp $");
     43 #endif
     44 #endif /* not lint */
     45 
     46 /*
     47  *
     48  *	 @@@        @@@    @@@@@@@@@@     @@@@@@@@@@@    @@@@@@@@@@@@
     49  *	 @@@        @@@   @@@@@@@@@@@@    @@@@@@@@@@@@   @@@@@@@@@@@@@
     50  *	 @@@        @@@  @@@@      @@@@   @@@@           @@@@ @@@  @@@@
     51  *	 @@@   @@   @@@  @@@        @@@   @@@            @@@  @@@   @@@
     52  *	 @@@  @@@@  @@@  @@@        @@@   @@@            @@@  @@@   @@@
     53  *	 @@@@ @@@@ @@@@  @@@        @@@   @@@            @@@  @@@   @@@
     54  *	  @@@@@@@@@@@@   @@@@      @@@@   @@@            @@@  @@@   @@@
     55  *	   @@@@  @@@@     @@@@@@@@@@@@    @@@            @@@  @@@   @@@
     56  *	    @@    @@       @@@@@@@@@@     @@@            @@@  @@@   @@@
     57  *
     58  *				 Eric P. Scott
     59  *			  Caltech High Energy Physics
     60  *				 October, 1980
     61  *
     62  */
     63 #include <sys/types.h>
     64 
     65 #include <ctype.h>
     66 #include <curses.h>
     67 #include <err.h>
     68 #include <limits.h>
     69 #include <signal.h>
     70 #include <stdbool.h>
     71 #include <stdio.h>
     72 #include <stdlib.h>
     73 #include <strings.h>
     74 #include <unistd.h>
     75 
     76 static const struct options {
     77 	int nopts;
     78 	int opts[3];
     79 }
     80 	normal[8] = {
     81 	{ 3, { 7, 0, 1 } },
     82 	{ 3, { 0, 1, 2 } },
     83 	{ 3, { 1, 2, 3 } },
     84 	{ 3, { 2, 3, 4 } },
     85 	{ 3, { 3, 4, 5 } },
     86 	{ 3, { 4, 5, 6 } },
     87 	{ 3, { 5, 6, 7 } },
     88 	{ 3, { 6, 7, 0 } }
     89 },	upper[8] = {
     90 	{ 1, { 1, 0, 0 } },
     91 	{ 2, { 1, 2, 0 } },
     92 	{ 0, { 0, 0, 0 } },
     93 	{ 0, { 0, 0, 0 } },
     94 	{ 0, { 0, 0, 0 } },
     95 	{ 2, { 4, 5, 0 } },
     96 	{ 1, { 5, 0, 0 } },
     97 	{ 2, { 1, 5, 0 } }
     98 },
     99 	left[8] = {
    100 	{ 0, { 0, 0, 0 } },
    101 	{ 0, { 0, 0, 0 } },
    102 	{ 0, { 0, 0, 0 } },
    103 	{ 2, { 2, 3, 0 } },
    104 	{ 1, { 3, 0, 0 } },
    105 	{ 2, { 3, 7, 0 } },
    106 	{ 1, { 7, 0, 0 } },
    107 	{ 2, { 7, 0, 0 } }
    108 },
    109 	right[8] = {
    110 	{ 1, { 7, 0, 0 } },
    111 	{ 2, { 3, 7, 0 } },
    112 	{ 1, { 3, 0, 0 } },
    113 	{ 2, { 3, 4, 0 } },
    114 	{ 0, { 0, 0, 0 } },
    115 	{ 0, { 0, 0, 0 } },
    116 	{ 0, { 0, 0, 0 } },
    117 	{ 2, { 6, 7, 0 } }
    118 },
    119 	lower[8] = {
    120 	{ 0, { 0, 0, 0 } },
    121 	{ 2, { 0, 1, 0 } },
    122 	{ 1, { 1, 0, 0 } },
    123 	{ 2, { 1, 5, 0 } },
    124 	{ 1, { 5, 0, 0 } },
    125 	{ 2, { 5, 6, 0 } },
    126 	{ 0, { 0, 0, 0 } },
    127 	{ 0, { 0, 0, 0 } }
    128 },
    129 	upleft[8] = {
    130 	{ 0, { 0, 0, 0 } },
    131 	{ 0, { 0, 0, 0 } },
    132 	{ 0, { 0, 0, 0 } },
    133 	{ 0, { 0, 0, 0 } },
    134 	{ 0, { 0, 0, 0 } },
    135 	{ 1, { 3, 0, 0 } },
    136 	{ 2, { 1, 3, 0 } },
    137 	{ 1, { 1, 0, 0 } }
    138 },
    139 	upright[8] = {
    140 	{ 2, { 3, 5, 0 } },
    141 	{ 1, { 3, 0, 0 } },
    142 	{ 0, { 0, 0, 0 } },
    143 	{ 0, { 0, 0, 0 } },
    144 	{ 0, { 0, 0, 0 } },
    145 	{ 0, { 0, 0, 0 } },
    146 	{ 0, { 0, 0, 0 } },
    147 	{ 1, { 5, 0, 0 } }
    148 },
    149 	lowleft[8] = {
    150 	{ 3, { 7, 0, 1 } },
    151 	{ 0, { 0, 0, 0 } },
    152 	{ 0, { 0, 0, 0 } },
    153 	{ 1, { 1, 0, 0 } },
    154 	{ 2, { 1, 7, 0 } },
    155 	{ 1, { 7, 0, 0 } },
    156 	{ 0, { 0, 0, 0 } },
    157 	{ 0, { 0, 0, 0 } }
    158 },
    159 	lowright[8] = {
    160 	{ 0, { 0, 0, 0 } },
    161 	{ 1, { 7, 0, 0 } },
    162 	{ 2, { 5, 7, 0 } },
    163 	{ 1, { 5, 0, 0 } },
    164 	{ 0, { 0, 0, 0 } },
    165 	{ 0, { 0, 0, 0 } },
    166 	{ 0, { 0, 0, 0 } },
    167 	{ 0, { 0, 0, 0 } }
    168 };
    169 
    170 
    171 static const char	flavor[] = {
    172 	'O', '*', '#', '$', '%', '0', '@', '~',
    173 	'+', 'w', ':', '^', '_', '&', 'x', 'o'
    174 };
    175 static const short	xinc[] = {
    176 	1,  1,  1,  0, -1, -1, -1,  0
    177 }, yinc[] = {
    178 	-1,  0,  1,  1,  1,  0, -1, -1
    179 };
    180 static struct	worm {
    181 	int orientation, head;
    182 	short *xpos, *ypos;
    183 } *worm;
    184 
    185 static volatile sig_atomic_t sig_caught = 0;
    186 
    187 static void nomem(void) __dead;
    188 static void onsig(int);
    189 
    190 int
    191 main(int argc, char *argv[])
    192 {
    193 	int x, y, h, n;
    194 	struct worm *w;
    195 	const struct options *op;
    196 	short *ip;
    197 	int CO, LI, last, bottom, ch, length, number, trail;
    198 	unsigned int seed;
    199 	short **ref;
    200 	const char *field;
    201 	char *ep;
    202 	unsigned int delay = 20000;
    203 	unsigned long ul;
    204 	bool argerror = false;
    205 
    206 	length = 16;
    207 	number = 3;
    208 	trail = ' ';
    209 	field = NULL;
    210 	seed = 0;
    211 	while ((ch = getopt(argc, argv, "d:fl:n:S:t")) != -1) {
    212 		switch(ch) {
    213 		case 'd':
    214 			ul = strtoul(optarg, &ep, 10);
    215 			if (ep != optarg) {
    216 				while (isspace(*(unsigned char *)ep))
    217 					ep++;
    218 			}
    219 			if (ep == optarg ||
    220 			    (*ep != '\0' &&
    221 				( ep[1] == '\0' ? (*ep != 'm' && *ep != 'u') :
    222 				( strcasecmp(ep, "ms") != 0 &&
    223 				  strcasecmp(ep, "us") != 0 )) )) {
    224 				    errx(1, "-d: invalid delay (%s)", optarg);
    225 			}
    226 			/*
    227 			 * if ul >= INT_MAX/1000 we don't need the *1000,
    228 			 * as even without that it will exceed the limit
    229 			 * just below and be treated as an error.
    230 			 * (This does assume >=32 bit int, but so does POSIX)
    231 			 */
    232 			if (*ep != 'u' && ul < INT_MAX / 1000)
    233 				ul *= 1000;  /* ms -> us */
    234 			if (ul > 1000*1000) {
    235 				errx(1,
    236 				   "-d: delay (%s) out of rannge [0 - 1000]",
    237 				   optarg);
    238 			}
    239 			delay = (unsigned int)ul;
    240 			continue;
    241 		case 'f':
    242 			field = "WORM";
    243 			continue;
    244 		case 'l':
    245 			ul = strtoul(optarg, &ep, 10);
    246 			if (ep == optarg || *ep != '\0' ||
    247 			    ul < 2 || ul > 1024) {
    248 				errx(1, "-l: invalid length (%s) [%d - %d].",
    249 				     optarg, 2, 1024);
    250 			}
    251 			length = (int)ul;
    252 			continue;
    253 		case 'n':
    254 			ul = strtoul(optarg, &ep, 10);
    255 			if (ep == optarg || *ep != '\0' ||
    256 			    ul < 1 || ul > INT_MAX / 10 ) {
    257 				errx(1, "-n: invalid number of worms (%s).",
    258 				    optarg);
    259 			}
    260 			/* upper bound is further limited later */
    261 			number = (int)ul;
    262 			continue;
    263 		case 'S':
    264 			ul = strtoul(optarg, &ep, 0);
    265 			if (ep == optarg || *ep != '\0' ||
    266 			    ul > UINT_MAX ) {
    267 				errx(1, "-S: invalid seed (%s).", optarg);
    268 			}
    269 			seed = (unsigned int)ul;
    270 			continue;
    271 		case 't':
    272 			trail = '.';
    273 			continue;
    274 		case '?':
    275 		default:
    276 			argerror = true;
    277 			break;
    278 		}
    279 		break;
    280 	}
    281 
    282 	if (argerror || argc > optind)
    283 		errx(1,
    284 		    "Usage: worms [-ft] [-d delay] [-l length] [-n number]");
    285 		/* -S omitted deliberately, not useful often enough */
    286 
    287 	if (!initscr())
    288 		errx(1, "couldn't initialize screen");
    289 	curs_set(0);
    290 	CO = COLS;
    291 	LI = LINES;
    292 
    293 	if (CO == 0 || LI == 0) {
    294 		endwin();
    295 		errx(1, "screen must be a rectangle, not (%dx%d)", CO, LI);
    296 	}
    297 	if (CO >= INT_MAX / LI) {
    298 		endwin();
    299 		errx(1, "screen (%dx%d) too large for worms", CO, LI);
    300 	}
    301 
    302 	/* now known that LI*CO cannot overflow an int => also not a long */
    303 
    304 	if (LI < 3 || CO < 3 || LI * CO < 40) {
    305 		/*
    306 		 * The placement algorithm is too weak for dimensions < 3.
    307 		 * Need at least 40 spaces so we can have (n > 1) worms
    308 		 * of a reasonable length, and still leave empty space.
    309 		 */
    310 		endwin();
    311 		errx(1, "screen (%dx%d) too small for worms", CO, LI);
    312 	}
    313 
    314 	ul = (unsigned long)CO * LI;
    315 	if ((unsigned long)length > ul / 20) {
    316 		endwin();
    317 		errx(1, "-l: worms loo long (%d) for screen; max: %lu",
    318 		    length, ul / 20);
    319 	}
    320 
    321 	ul /= (length * 3);	/* no more than 33% arena occupancy */
    322 	if ((unsigned long)(unsigned)number > ul) {
    323 		endwin();
    324 		errx(1, "-n: too many worms (%d) max: %lu", number, ul);
    325 	}
    326 	if (!(worm = calloc((size_t)number, sizeof(struct worm))))
    327 		nomem();
    328 
    329 	last = CO - 1;
    330 	bottom = LI - 1;
    331 	if (!(ip = malloc((size_t)(LI * CO * sizeof(short)))))
    332 		nomem();
    333 	if (!(ref = malloc((size_t)(LI * sizeof(short *)))))
    334 		nomem();
    335 	for (n = 0; n < LI; ++n) {
    336 		ref[n] = ip;
    337 		ip += CO;
    338 	}
    339 	for (ip = ref[0], n = LI * CO; --n >= 0;)
    340 		*ip++ = 0;
    341 	for (n = number, w = &worm[0]; --n >= 0; w++) {
    342 		w->orientation = w->head = 0;
    343 		if (!(ip = malloc((size_t)(length * sizeof(short)))))
    344 			nomem();
    345 		w->xpos = ip;
    346 		for (x = length; --x >= 0;)
    347 			*ip++ = -1;
    348 		if (!(ip = malloc((size_t)(length * sizeof(short)))))
    349 			nomem();
    350 		w->ypos = ip;
    351 		for (y = length; --y >= 0;)
    352 			*ip++ = -1;
    353 	}
    354 
    355 	(void)signal(SIGHUP, onsig);
    356 	(void)signal(SIGINT, onsig);
    357 	(void)signal(SIGQUIT, onsig);
    358 	(void)signal(SIGTSTP, onsig);
    359 	(void)signal(SIGTERM, onsig);
    360 
    361 	if (field) {
    362 		const char *p = field;
    363 
    364 		for (y = LI; --y >= 0;) {
    365 			for (x = CO; --x >= 0;) {
    366 				addch(*p++);
    367 				if (!*p)
    368 					p = field;
    369 			}
    370 			refresh();
    371 		}
    372 	}
    373 	srandom(seed ? seed : arc4random());
    374 	for (;;) {
    375 		refresh();
    376 		if (sig_caught) {
    377 			endwin();
    378 			exit(0);
    379 		}
    380 		if (delay) {
    381 			if (delay % 1000000 != 0)
    382 				usleep(delay % 1000000);
    383 			if (delay >= 1000000)
    384 				sleep(delay / 1000000);
    385 		}
    386 		for (n = 0, w = &worm[0]; n < number; n++, w++) {
    387 			if ((x = w->xpos[h = w->head]) < 0) {
    388 				mvaddch(y = w->ypos[h] = bottom,
    389 					x = w->xpos[h] = 0,
    390 					flavor[n % sizeof(flavor)]);
    391 				ref[y][x]++;
    392 			}
    393 			else
    394 				y = w->ypos[h];
    395 			if (++h == length)
    396 				h = 0;
    397 			if (w->xpos[w->head = h] >= 0) {
    398 				int x1, y1;
    399 
    400 				x1 = w->xpos[h];
    401 				y1 = w->ypos[h];
    402 				if (--ref[y1][x1] == 0) {
    403 					mvaddch(y1, x1, trail);
    404 				}
    405 			}
    406 
    407 			op = &(!x
    408 				? (!y
    409 				    ? upleft
    410 				    : (y == bottom ? lowleft : left))
    411 				: (x == last
    412 				    ? (!y ? upright
    413 					  : (y == bottom ? lowright : right))
    414 				    : (!y ? upper
    415 					  : (y == bottom ? lower : normal)))
    416 			      )[w->orientation];
    417 
    418 			switch (op->nopts) {
    419 			case 0:
    420 				refresh();
    421 				endwin();
    422 				abort();
    423 				/* NOTREACHED */
    424 			case 1:
    425 				w->orientation = op->opts[0];
    426 				break;
    427 			default:
    428 				w->orientation =
    429 				    op->opts[(int)random() % op->nopts];
    430 			}
    431 			mvaddch(y += yinc[w->orientation],
    432 				x += xinc[w->orientation],
    433 				flavor[n % sizeof(flavor)]);
    434 			ref[w->ypos[h] = y][w->xpos[h] = x]++;
    435 		}
    436 	}
    437 }
    438 
    439 static void
    440 onsig(int signo __unused)
    441 {
    442 	sig_caught = 1;
    443 }
    444 
    445 /* This is never called before curses is initialised */
    446 static void
    447 nomem(void)
    448 {
    449 	endwin();
    450 	errx(1, "not enough memory.");
    451 }
    452