Module Name: src Committed By: kre Date: Wed Apr 26 22:58:09 UTC 2023
Modified Files: src/games/worms: worms.6 worms.c Log Message: Add a little optional colour, optionally distinguish worm heads, and optionally randomise worm lengths. Just exit instead of continuing with a nonsense display if the window shrinks (and for consistency if it grows). Most of the ideas and code from RVP. Bugs and man page mangling from me. To generate a diff of this commit: cvs rdiff -u -r1.17 -r1.18 src/games/worms/worms.6 cvs rdiff -u -r1.29 -r1.30 src/games/worms/worms.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/games/worms/worms.6 diff -u src/games/worms/worms.6:1.17 src/games/worms/worms.6:1.18 --- src/games/worms/worms.6:1.17 Tue Apr 18 15:02:22 2023 +++ src/games/worms/worms.6 Wed Apr 26 22:58:09 2023 @@ -1,4 +1,4 @@ -.\" $NetBSD: worms.6,v 1.17 2023/04/18 15:02:22 kre Exp $ +.\" $NetBSD: worms.6,v 1.18 2023/04/26 22:58:09 kre Exp $ .\" .\" Copyright (c) 1989, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" @(#)worms.6 8.1 (Berkeley) 5/31/93 .\" -.Dd April 17, 2023 +.Dd April 26, 2023 .Dt WORMS 6 .Os .Sh NAME @@ -37,7 +37,7 @@ .Nd animate worms on a display terminal .Sh SYNOPSIS .Nm -.Op Fl ft +.Op Fl CfHrt .Op Fl d Ar delay .Op Fl l Ar length .Op Fl n Ar number @@ -50,7 +50,11 @@ version of the DEC-2136 program .Dq worms . .Pp The options are as follows: -.Bl -tag -width Fl +.Bl -compact -tag -width Fl +.Pp +.It Fl C +Use colours, if available, to make the worms easier to distinguish. +.Pp .It Fl d Ar delay Specifies a .Ar delay , @@ -58,24 +62,53 @@ in milliseconds, between each update. This is useful for fast terminals. Reasonable values are around 20-200; the default is 20. +.Pp .It Fl f Makes a .Dq field for the worm(s) to eat. +.Pp +.It Fl H +Display the head of the worm differently than its body. +.Pp .It Fl l Ar length +.It Fl l Ar min Ns \(mi Ns Ar max Specifies the .Ar length of each worm; the default is 16, the minimum is 2. +In the second form, worm lengths are randomly chosen +between +.Ar min +.Pq which must be at least 2 +and +.Ar max . +The +.Ar max +worm length will be reduced if required by the screen +size and the +.Ar number +of worms selected. +This option overrides any earlier +.Fl r . +.Pp .It Fl n Ar number Specifies the .Ar number of worms; the default is 3. There must be at least one. +.Pp +.It Fl r +Use random lengths for the worms, within a range of +sizes chosen to suit the screen size. +Note this option overrides any earlier +.Fl l . +.Pp .It Fl S Ar seed Provide an integer .Ar seed for the random number generator. Specifying zero (0, the default) causes a random seed to be used. +.Pp .It Fl t Makes each worm leave a trail behind it. .El Index: src/games/worms/worms.c diff -u src/games/worms/worms.c:1.29 src/games/worms/worms.c:1.30 --- src/games/worms/worms.c:1.29 Wed Apr 19 09:39:29 2023 +++ src/games/worms/worms.c Wed Apr 26 22:58:09 2023 @@ -1,4 +1,4 @@ -/* $NetBSD: worms.c,v 1.29 2023/04/19 09:39:29 kre Exp $ */ +/* $NetBSD: worms.c,v 1.30 2023/04/26 22:58:09 kre Exp $ */ /* * Copyright (c) 1980, 1993 @@ -39,7 +39,7 @@ __COPYRIGHT("@(#) Copyright (c) 1980, 19 #if 0 static char sccsid[] = "@(#)worms.c 8.1 (Berkeley) 5/31/93"; #else -__RCSID("$NetBSD: worms.c,v 1.29 2023/04/19 09:39:29 kre Exp $"); +__RCSID("$NetBSD: worms.c,v 1.30 2023/04/26 22:58:09 kre Exp $"); #endif #endif /* not lint */ @@ -71,6 +71,7 @@ __RCSID("$NetBSD: worms.c,v 1.29 2023/04 #include <stdio.h> #include <stdlib.h> #include <strings.h> +#include <term.h> #include <unistd.h> static const struct options { @@ -86,7 +87,8 @@ static const struct options { { 3, { 4, 5, 6 } }, { 3, { 5, 6, 7 } }, { 3, { 6, 7, 0 } } -}, upper[8] = { +}, + upper[8] = { { 1, { 1, 0, 0 } }, { 2, { 1, 2, 0 } }, { 0, { 0, 0, 0 } }, @@ -166,50 +168,110 @@ static const struct options { { 0, { 0, 0, 0 } }, { 0, { 0, 0, 0 } } }; - - static const char flavor[] = { - 'O', '*', '#', '$', '%', '0', '@', '~', - '+', 'w', ':', '^', '_', '&', 'x', 'o' + 'O', '*', '#', '$', '%', '0', 'o', '~', + '+', 'x', ':', '^', '_', '&', '@', 'w' }; +static const int flavors = __arraycount(flavor); + static const short xinc[] = { 1, 1, 1, 0, -1, -1, -1, 0 }, yinc[] = { -1, 0, 1, 1, 1, 0, -1, -1 }; static struct worm { - int orientation, head; + int orientation, head, len; short *xpos, *ypos; + chtype ch, attr; } *worm; -static volatile sig_atomic_t sig_caught = 0; +static volatile sig_atomic_t sig_caught; +static int initclr(int**); static void nomem(void) __dead; static void onsig(int); +static int worm_length(int, int); int main(int argc, char *argv[]) { - int x, y, h, n; - struct worm *w; + int CO, LI, last, bottom, ch, number, trail; + int x, y, h, n, nc; + int maxlength, minlength; + unsigned int seed, delay; const struct options *op; - short *ip; - int CO, LI, last, bottom, ch, length, number, trail; - unsigned int seed; + struct worm *w; short **ref; + short *ip; const char *field; char *ep; - unsigned int delay = 20000; - unsigned long ul; + unsigned long ul, up; bool argerror = false; + bool docolour = false; /* -C, use coloured worms */ + bool docaput = false; /* -H, show which end of worm is head */ + int *ctab = NULL; - length = 16; + delay = 20000; + maxlength = minlength = 16; number = 3; + seed = 0; trail = ' '; field = NULL; - seed = 0; - while ((ch = getopt(argc, argv, "d:fl:n:S:t")) != -1) { + + if ((ep = getenv("WORMS")) != NULL) { + ul = up = 0; + while ((ch = *ep++) != '\0') { + switch (ch) { + case 'C': + docolour = !docolour; + continue; + case 'f': + if (field) + field = NULL; + else + field = "WORM"; + continue; + case 'H': + docaput = !docaput; + continue; + case 'r': + minlength = 5; + maxlength = 0; + continue; + case 't': + if (trail == ' ') + trail = '.'; + else + trail = ' '; + continue; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (up > 1) + continue; + if (ul >= 100000) /* 1/10 second, in us */ + continue; + ul *= 10; + ul += (ch - '0'); + up = 1; + continue; + case 'm': + if (up == 1 && ul <= 1000) + ul *= 1000; + up += 2; + continue; + default: + continue; + } + } + if ((up & 1) != 0) /* up == 1 || up == 3 */ + delay = ul; + } + + while ((ch = getopt(argc, argv, "Cd:fHl:n:rS:t")) != -1) { switch(ch) { + case 'C': + docolour = !docolour; + continue; case 'd': ul = strtoul(optarg, &ep, 10); if (ep != optarg) { @@ -239,16 +301,29 @@ main(int argc, char *argv[]) delay = (unsigned int)ul; continue; case 'f': - field = "WORM"; + if (field == NULL) + field = "WORM"; + else + field = NULL; + continue; + case 'H': + docaput = !docaput; continue; case 'l': - ul = strtoul(optarg, &ep, 10); + up = ul = strtoul(optarg, &ep, 10); + if (ep != optarg) { + while (isspace(*(unsigned char *)ep)) + ep++; + if (*ep == '-') + up = strtoul(++ep, &ep, 10); + } if (ep == optarg || *ep != '\0' || - ul < 2 || ul > 1024) { + ul < 2 || up < ul || up > 1024) { errx(1, "-l: invalid length (%s) [%d - %d].", optarg, 2, 1024); } - length = (int)ul; + minlength = (int)ul; + maxlength = (int)up; continue; case 'n': ul = strtoul(optarg, &ep, 10); @@ -260,6 +335,10 @@ main(int argc, char *argv[]) /* upper bound is further limited later */ number = (int)ul; continue; + case 'r': + minlength = 5; + maxlength = 0; + continue; case 'S': ul = strtoul(optarg, &ep, 0); if (ep == optarg || *ep != '\0' || @@ -269,7 +348,10 @@ main(int argc, char *argv[]) seed = (unsigned int)ul; continue; case 't': - trail = '.'; + if (trail == ' ') + trail = '.'; + else + trail = ' '; continue; case '?': default: @@ -281,12 +363,13 @@ main(int argc, char *argv[]) if (argerror || argc > optind) errx(1, - "Usage: worms [-ft] [-d delay] [-l length] [-n number]"); + "Usage: worms [-CfHrt] [-d delay] [-l length] [-n number]"); /* -S omitted deliberately, not useful often enough */ if (!initscr()) errx(1, "couldn't initialize screen"); curs_set(0); + nc = docolour ? initclr(&ctab) : 0; CO = COLS; LI = LINES; @@ -294,7 +377,12 @@ main(int argc, char *argv[]) endwin(); errx(1, "screen must be a rectangle, not (%dx%d)", CO, LI); } - if (CO >= INT_MAX / LI) { + + /* + * The "sizeof(short *)" noise is to abslutely guarantee + * that a LI * CO * sizeof(short *) cannot overflow an int + */ + if (CO >= (int)(INT_MAX / (2 * sizeof(short *)) / LI)) { endwin(); errx(1, "screen (%dx%d) too large for worms", CO, LI); } @@ -311,14 +399,25 @@ main(int argc, char *argv[]) errx(1, "screen (%dx%d) too small for worms", CO, LI); } + if (maxlength == 0) + maxlength = minlength + (CO * LI / 40); + ul = (unsigned long)CO * LI; - if ((unsigned long)length > ul / 20) { + if ((unsigned long)maxlength > ul / 20) { endwin(); errx(1, "-l: worms too long (%d) for screen; max: %lu", - length, ul / 20); + maxlength, ul / 20); + } + + ul /= maxlength * 3; /* no more than 33% arena occupancy */ + + if ((unsigned long)(unsigned)number > ul && maxlength > minlength) { + maxlength = CO * LI / 3 / number; + if (maxlength < minlength) + maxlength = minlength; + ul = (CO * LI) / ((minlength + maxlength)/2 * 3);; } - ul /= (length * 3); /* no more than 33% arena occupancy */ if ((unsigned long)(unsigned)number > ul) { endwin(); errx(1, "-n: too many worms (%d) max: %lu", number, ul); @@ -326,37 +425,48 @@ main(int argc, char *argv[]) if (!(worm = calloc((size_t)number, sizeof(struct worm)))) nomem(); + srandom(seed ? seed : arc4random()); + last = CO - 1; bottom = LI - 1; - if (!(ip = malloc((size_t)(LI * CO * sizeof(short))))) + + if (!(ip = calloc(LI * CO, sizeof(short)))) nomem(); - if (!(ref = malloc((size_t)(LI * sizeof(short *))))) + if (!(ref = malloc((size_t)LI * sizeof(short *)))) nomem(); for (n = 0; n < LI; ++n) { ref[n] = ip; ip += CO; } - for (ip = ref[0], n = LI * CO; --n >= 0;) - *ip++ = 0; + for (n = number, w = &worm[0]; --n >= 0; w++) { + int i; + w->orientation = w->head = 0; - if (!(ip = malloc((size_t)(length * sizeof(short))))) + w->len = worm_length(minlength, maxlength); + w->attr = nc ? ctab[n % nc] : 0; + i = (nc && number > flavors ? n / nc : n) % flavors; + w->ch = flavor[i]; + + if (!(ip = malloc((size_t)(w->len * sizeof(short))))) nomem(); w->xpos = ip; - for (x = length; --x >= 0;) + for (x = w->len; --x >= 0;) *ip++ = -1; - if (!(ip = malloc((size_t)(length * sizeof(short))))) + if (!(ip = malloc((size_t)(w->len * sizeof(short))))) nomem(); w->ypos = ip; - for (y = length; --y >= 0;) + for (y = w->len; --y >= 0;) *ip++ = -1; } + free(ctab); /* not needed any more */ (void)signal(SIGHUP, onsig); (void)signal(SIGINT, onsig); (void)signal(SIGQUIT, onsig); - (void)signal(SIGTSTP, onsig); (void)signal(SIGTERM, onsig); + (void)signal(SIGTSTP, onsig); + (void)signal(SIGWINCH, onsig); if (field) { const char *p = field; @@ -367,10 +477,9 @@ main(int argc, char *argv[]) if (!*p) p = field; } - refresh(); } } - srandom(seed ? seed : arc4random()); + for (;;) { refresh(); if (sig_caught) { @@ -384,24 +493,23 @@ main(int argc, char *argv[]) sleep(delay / 1000000); } for (n = 0, w = &worm[0]; n < number; n++, w++) { + chtype c = docaput ? (w->ch == '@' ? '0' : '@') : w->ch; + if ((x = w->xpos[h = w->head]) < 0) { mvaddch(y = w->ypos[h] = bottom, - x = w->xpos[h] = 0, - flavor[n % sizeof(flavor)]); + x = w->xpos[h] = 0, c | w->attr); ref[y][x]++; - } - else + } else y = w->ypos[h]; - if (++h == length) + if (++h == w->len) h = 0; if (w->xpos[w->head = h] >= 0) { int x1, y1; x1 = w->xpos[h]; y1 = w->ypos[h]; - if (--ref[y1][x1] == 0) { + if (--ref[y1][x1] == 0) mvaddch(y1, x1, trail); - } } op = &(!x @@ -417,7 +525,6 @@ main(int argc, char *argv[]) switch (op->nopts) { case 0: - refresh(); endwin(); abort(); /* NOTREACHED */ @@ -430,10 +537,79 @@ main(int argc, char *argv[]) } mvaddch(y += yinc[w->orientation], x += xinc[w->orientation], - flavor[n % sizeof(flavor)]); + c | w->attr); ref[w->ypos[h] = y][w->xpos[h] = x]++; + if (docaput && w->len > 1) { + int prev = (h ? h : w->len) - 1; + + mvaddch(w->ypos[prev], w->xpos[prev], + w->ch | w->attr); + } + } + } +} + +static int +initclr(int** ctab) +{ + int *ip, clr[] = { + COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, + COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE + }, attr[] = { + A_NORMAL, A_BOLD, A_DIM + }; + int nattr = __arraycount(attr); + int nclr = __arraycount(clr); + int nc = 0; + + /* terminfo first */ + char* s; + bool canbold = (s = tigetstr("bold")) != (char* )-1 && s != NULL; + bool candim = (s = tigetstr("dim")) != (char* )-1 && s != NULL; + +#ifdef DO_TERMCAP + /* termcap if terminfo fails */ + canbold = canbold || (s = tgetstr("md", NULL)) != NULL; + candim = candim || (s = tgetstr("mh", NULL)) != NULL; +#endif + + if (has_colors() == FALSE) + return 0; + use_default_colors(); + if (start_color() == ERR) + return 0; + if ((*ctab = calloc(COLOR_PAIRS, sizeof(int))) == NULL) + nomem(); + ip = *ctab; + + for (int i = 0; i < nattr; i++) { + if (!canbold && attr[i] == A_BOLD) + continue; + if (!candim && attr[i] == A_DIM) + continue; + + for (int j = 0; j < nclr; j++) { + if (clr[j] == COLOR_BLACK && attr[i] != A_BOLD) + continue; /* invisible */ + if (nc + 1 >= COLOR_PAIRS) + break; + if (init_pair(nc + 1, clr[j], -1) == ERR) + break; + *ip++ = COLOR_PAIR(nc + 1) | attr[i]; + nc++; } } + + return nc; +} + +static int +worm_length(int low, int high) +{ + if (low >= high) + return low; + + return low + (random() % (high - low + 1)); } static void