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.
This commit is contained in:
kre 2023-04-26 22:58:09 +00:00
parent 2dbb90eecc
commit 8552e825c7
2 changed files with 262 additions and 53 deletions

View File

@ -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

View File

@ -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, 1993\
#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/19 09:39:29 kre Exp $");
#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,12 +537,81 @@ 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
onsig(int signo __unused)
{