1/****************************************************************************
2 * Copyright (c) 1998-2007,2008 Free Software Foundation, Inc.              *
3 *                                                                          *
4 * Permission is hereby granted, free of charge, to any person obtaining a  *
5 * copy of this software and associated documentation files (the            *
6 * "Software"), to deal in the Software without restriction, including      *
7 * without limitation the rights to use, copy, modify, merge, publish,      *
8 * distribute, distribute with modifications, sublicense, and/or sell       *
9 * copies of the Software, and to permit persons to whom the Software is    *
10 * furnished to do so, subject to the following conditions:                 *
11 *                                                                          *
12 * The above copyright notice and this permission notice shall be included  *
13 * in all copies or substantial portions of the Software.                   *
14 *                                                                          *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22 *                                                                          *
23 * Except as contained in this notice, the name(s) of the above copyright   *
24 * holders shall not be used in advertising or otherwise to promote the     *
25 * sale, use or other dealings in this Software without prior written       *
26 * authorization.                                                           *
27 ****************************************************************************/
28/*
29
30	 @@@        @@@    @@@@@@@@@@     @@@@@@@@@@@    @@@@@@@@@@@@
31	 @@@        @@@   @@@@@@@@@@@@    @@@@@@@@@@@@   @@@@@@@@@@@@@
32	 @@@        @@@  @@@@      @@@@   @@@@           @@@@ @@@  @@@@
33	 @@@   @@   @@@  @@@        @@@   @@@            @@@  @@@   @@@
34	 @@@  @@@@  @@@  @@@        @@@   @@@            @@@  @@@   @@@
35	 @@@@ @@@@ @@@@  @@@        @@@   @@@            @@@  @@@   @@@
36	  @@@@@@@@@@@@   @@@@      @@@@   @@@            @@@  @@@   @@@
37	   @@@@  @@@@     @@@@@@@@@@@@    @@@            @@@  @@@   @@@
38	    @@    @@       @@@@@@@@@@     @@@            @@@  @@@   @@@
39
40				 Eric P. Scott
41			  Caltech High Energy Physics
42				 October, 1980
43
44		Hacks to turn this into a test frame for cursor movement:
45			Eric S. Raymond <esr@snark.thyrsus.com>
46				January, 1995
47
48		July 1995 (esr): worms is now in living color! :-)
49
50Options:
51	-f			fill screen with copies of 'WORM' at start.
52	-l <n>			set worm length
53	-n <n>			set number of worms
54	-t			make worms leave droppings
55	-T <start> <end>	set trace interval
56	-S			set single-stepping during trace interval
57	-N			suppress cursor-movement optimization
58
59  This program makes a good torture-test for the ncurses cursor-optimization
60  code.  You can use -T to set the worm move interval over which movement
61  traces will be dumped.  The program stops and waits for one character of
62  input at the beginning and end of the interval.
63
64  $Id: worm.c,v 1.58 2008/10/04 21:54:09 tom Exp $
65*/
66
67#include <test.priv.h>
68
69#ifdef USE_PTHREADS
70#include <pthread.h>
71#endif
72
73WANT_USE_WINDOW();
74
75#define MAX_WORMS	40
76#define MAX_LENGTH	1024
77
78static chtype flavor[] =
79{
80    'O', '*', '#', '$', '%', '0', '@',
81};
82static const short xinc[] =
83{
84    1, 1, 1, 0, -1, -1, -1, 0
85}, yinc[] =
86{
87    -1, 0, 1, 1, 1, 0, -1, -1
88};
89
90typedef struct worm {
91    int orientation;
92    int head;
93    short *xpos;
94    short *ypos;
95    chtype attrs;
96#ifdef USE_PTHREADS
97    pthread_t thread;
98#endif
99} WORM;
100
101static unsigned long sequence = 0;
102static bool quitting = FALSE;
103
104static WORM worm[MAX_WORMS];
105static short **refs;
106static int last_x, last_y;
107
108static const char *field;
109static int length = 16, number = 3;
110static chtype trail = ' ';
111
112static unsigned pending;
113#ifdef TRACE
114static int generation, trace_start, trace_end;
115#endif /* TRACE */
116/* *INDENT-OFF* */
117static const struct options {
118    int nopts;
119    int opts[3];
120} normal[8]={
121    { 3, { 7, 0, 1 } },
122    { 3, { 0, 1, 2 } },
123    { 3, { 1, 2, 3 } },
124    { 3, { 2, 3, 4 } },
125    { 3, { 3, 4, 5 } },
126    { 3, { 4, 5, 6 } },
127    { 3, { 5, 6, 7 } },
128    { 3, { 6, 7, 0 } }
129}, upper[8]={
130    { 1, { 1, 0, 0 } },
131    { 2, { 1, 2, 0 } },
132    { 0, { 0, 0, 0 } },
133    { 0, { 0, 0, 0 } },
134    { 0, { 0, 0, 0 } },
135    { 2, { 4, 5, 0 } },
136    { 1, { 5, 0, 0 } },
137    { 2, { 1, 5, 0 } }
138}, left[8]={
139    { 0, { 0, 0, 0 } },
140    { 0, { 0, 0, 0 } },
141    { 0, { 0, 0, 0 } },
142    { 2, { 2, 3, 0 } },
143    { 1, { 3, 0, 0 } },
144    { 2, { 3, 7, 0 } },
145    { 1, { 7, 0, 0 } },
146    { 2, { 7, 0, 0 } }
147}, right[8]={
148    { 1, { 7, 0, 0 } },
149    { 2, { 3, 7, 0 } },
150    { 1, { 3, 0, 0 } },
151    { 2, { 3, 4, 0 } },
152    { 0, { 0, 0, 0 } },
153    { 0, { 0, 0, 0 } },
154    { 0, { 0, 0, 0 } },
155    { 2, { 6, 7, 0 } }
156}, lower[8]={
157    { 0, { 0, 0, 0 } },
158    { 2, { 0, 1, 0 } },
159    { 1, { 1, 0, 0 } },
160    { 2, { 1, 5, 0 } },
161    { 1, { 5, 0, 0 } },
162    { 2, { 5, 6, 0 } },
163    { 0, { 0, 0, 0 } },
164    { 0, { 0, 0, 0 } }
165}, upleft[8]={
166    { 0, { 0, 0, 0 } },
167    { 0, { 0, 0, 0 } },
168    { 0, { 0, 0, 0 } },
169    { 0, { 0, 0, 0 } },
170    { 0, { 0, 0, 0 } },
171    { 1, { 3, 0, 0 } },
172    { 2, { 1, 3, 0 } },
173    { 1, { 1, 0, 0 } }
174}, upright[8]={
175    { 2, { 3, 5, 0 } },
176    { 1, { 3, 0, 0 } },
177    { 0, { 0, 0, 0 } },
178    { 0, { 0, 0, 0 } },
179    { 0, { 0, 0, 0 } },
180    { 0, { 0, 0, 0 } },
181    { 0, { 0, 0, 0 } },
182    { 1, { 5, 0, 0 } }
183}, lowleft[8]={
184    { 3, { 7, 0, 1 } },
185    { 0, { 0, 0, 0 } },
186    { 0, { 0, 0, 0 } },
187    { 1, { 1, 0, 0 } },
188    { 2, { 1, 7, 0 } },
189    { 1, { 7, 0, 0 } },
190    { 0, { 0, 0, 0 } },
191    { 0, { 0, 0, 0 } }
192}, lowright[8]={
193    { 0, { 0, 0, 0 } },
194    { 1, { 7, 0, 0 } },
195    { 2, { 5, 7, 0 } },
196    { 1, { 5, 0, 0 } },
197    { 0, { 0, 0, 0 } },
198    { 0, { 0, 0, 0 } },
199    { 0, { 0, 0, 0 } },
200    { 0, { 0, 0, 0 } }
201};
202/* *INDENT-ON* */
203
204static void
205cleanup(void)
206{
207    USING_WINDOW(stdscr, wrefresh);
208    curs_set(1);
209    endwin();
210}
211
212static RETSIGTYPE
213onsig(int sig GCC_UNUSED)
214{
215    cleanup();
216    ExitProgram(EXIT_FAILURE);
217}
218
219static float
220ranf(void)
221{
222    long r = (rand() & 077777);
223    return ((float) r / 32768.);
224}
225
226static int
227draw_worm(WINDOW *win, void *data)
228{
229    WORM *w = (WORM *) data;
230    const struct options *op;
231    unsigned mask = ~(1 << (w - worm));
232    chtype attrs = w->attrs | ((mask & pending) ? A_REVERSE : 0);
233
234    int x;
235    int y;
236    int h;
237
238    bool done = FALSE;
239
240    if ((x = w->xpos[h = w->head]) < 0) {
241	wmove(win, y = w->ypos[h] = last_y, x = w->xpos[h] = 0);
242	waddch(win, attrs);
243	refs[y][x]++;
244    } else {
245	y = w->ypos[h];
246    }
247
248    if (x > last_x)
249	x = last_x;
250    if (y > last_y)
251	y = last_y;
252
253    if (++h == length)
254	h = 0;
255
256    if (w->xpos[w->head = h] >= 0) {
257	int x1, y1;
258	x1 = w->xpos[h];
259	y1 = w->ypos[h];
260	if (y1 < LINES
261	    && x1 < COLS
262	    && --refs[y1][x1] == 0) {
263	    wmove(win, y1, x1);
264	    waddch(win, trail);
265	}
266    }
267
268    op = &(x == 0
269	   ? (y == 0
270	      ? upleft
271	      : (y == last_y
272		 ? lowleft
273		 : left))
274	   : (x == last_x
275	      ? (y == 0
276		 ? upright
277		 : (y == last_y
278		    ? lowright
279		    : right))
280	      : (y == 0
281		 ? upper
282		 : (y == last_y
283		    ? lower
284		    : normal))))[w->orientation];
285
286    switch (op->nopts) {
287    case 0:
288	done = TRUE;
289	break;
290    case 1:
291	w->orientation = op->opts[0];
292	break;
293    default:
294	w->orientation = op->opts[(int) (ranf() * (float) op->nopts)];
295	break;
296    }
297
298    if (!done) {
299	x += xinc[w->orientation];
300	y += yinc[w->orientation];
301	wmove(win, y, x);
302
303	if (y < 0)
304	    y = 0;
305	waddch(win, attrs);
306
307	w->ypos[h] = y;
308	w->xpos[h] = x;
309	refs[y][x]++;
310    }
311
312    return done;
313}
314
315#ifdef USE_PTHREADS
316static bool
317quit_worm(int bitnum)
318{
319    pending |= (1 << bitnum);
320    napms(10);			/* let the other thread(s) have a chance */
321    pending &= ~(1 << bitnum);
322    return quitting;
323}
324
325static void *
326start_worm(void *arg)
327{
328    unsigned long compare = 0;
329    Trace(("start_worm"));
330    while (!quit_worm(((struct worm *) arg) - worm)) {
331	while (compare < sequence) {
332	    ++compare;
333	    use_window(stdscr, draw_worm, arg);
334	}
335    }
336    Trace(("...start_worm (done)"));
337    return NULL;
338}
339#endif
340
341static bool
342draw_all_worms(void)
343{
344    bool done = FALSE;
345    int n;
346    struct worm *w;
347
348#ifdef USE_PTHREADS
349    static bool first = TRUE;
350    if (first) {
351	first = FALSE;
352	for (n = 0, w = &worm[0]; n < number; n++, w++) {
353	    int rc;
354	    rc = pthread_create(&(w->thread), NULL, start_worm, w);
355	}
356    }
357#else
358    for (n = 0, w = &worm[0]; n < number; n++, w++) {
359	if (USING_WINDOW2(stdscr, draw_worm, w))
360	    done = TRUE;
361    }
362#endif
363    return done;
364}
365
366static int
367get_input(void)
368{
369    int ch;
370    ch = USING_WINDOW(stdscr, wgetch);
371    return ch;
372}
373
374#ifdef KEY_RESIZE
375static int
376update_refs(WINDOW *win)
377{
378    int x, y;
379
380    (void) win;
381    if (last_x != COLS - 1) {
382	for (y = 0; y <= last_y; y++) {
383	    refs[y] = typeRealloc(short, COLS, refs[y]);
384	    for (x = last_x + 1; x < COLS; x++)
385		refs[y][x] = 0;
386	}
387	last_x = COLS - 1;
388    }
389    if (last_y != LINES - 1) {
390	for (y = LINES; y <= last_y; y++)
391	    free(refs[y]);
392	refs = typeRealloc(short *, LINES, refs);
393	for (y = last_y + 1; y < LINES; y++) {
394	    refs[y] = typeMalloc(short, COLS);
395	    for (x = 0; x < COLS; x++)
396		refs[y][x] = 0;
397	}
398	last_y = LINES - 1;
399    }
400    return OK;
401}
402#endif
403
404int
405main(int argc, char *argv[])
406{
407    int x, y;
408    int n;
409    struct worm *w;
410    short *ip;
411    bool done = FALSE;
412
413    setlocale(LC_ALL, "");
414
415    for (x = 1; x < argc; x++) {
416	char *p;
417	p = argv[x];
418	if (*p == '-')
419	    p++;
420	switch (*p) {
421	case 'f':
422	    field = "WORM";
423	    break;
424	case 'l':
425	    if (++x == argc)
426		goto usage;
427	    if ((length = atoi(argv[x])) < 2 || length > MAX_LENGTH) {
428		fprintf(stderr, "%s: Invalid length\n", *argv);
429		ExitProgram(EXIT_FAILURE);
430	    }
431	    break;
432	case 'n':
433	    if (++x == argc)
434		goto usage;
435	    if ((number = atoi(argv[x])) < 1 || number > MAX_WORMS) {
436		fprintf(stderr, "%s: Invalid number of worms\n", *argv);
437		ExitProgram(EXIT_FAILURE);
438	    }
439	    break;
440	case 't':
441	    trail = '.';
442	    break;
443#ifdef TRACE
444	case 'T':
445	    trace_start = atoi(argv[++x]);
446	    trace_end = atoi(argv[++x]);
447	    break;
448	case 'N':
449	    _nc_optimize_enable ^= OPTIMIZE_ALL;	/* declared by ncurses */
450	    break;
451#endif /* TRACE */
452	default:
453	  usage:
454	    fprintf(stderr,
455		    "usage: %s [-field] [-length #] [-number #] [-trail]\n", *argv);
456	    ExitProgram(EXIT_FAILURE);
457	}
458    }
459
460    signal(SIGINT, onsig);
461    initscr();
462    noecho();
463    cbreak();
464    nonl();
465
466    curs_set(0);
467
468    last_y = LINES - 1;
469    last_x = COLS - 1;
470
471#ifdef A_COLOR
472    if (has_colors()) {
473	int bg = COLOR_BLACK;
474	start_color();
475#if HAVE_USE_DEFAULT_COLORS
476	if (use_default_colors() == OK)
477	    bg = -1;
478#endif
479
480#define SET_COLOR(num, fg) \
481	    init_pair(num+1, fg, bg); \
482	    flavor[num] |= COLOR_PAIR(num+1) | A_BOLD
483
484	SET_COLOR(0, COLOR_GREEN);
485	SET_COLOR(1, COLOR_RED);
486	SET_COLOR(2, COLOR_CYAN);
487	SET_COLOR(3, COLOR_WHITE);
488	SET_COLOR(4, COLOR_MAGENTA);
489	SET_COLOR(5, COLOR_BLUE);
490	SET_COLOR(6, COLOR_YELLOW);
491    }
492#endif /* A_COLOR */
493
494    refs = typeMalloc(short *, LINES);
495    for (y = 0; y < LINES; y++) {
496	refs[y] = typeMalloc(short, COLS);
497	for (x = 0; x < COLS; x++) {
498	    refs[y][x] = 0;
499	}
500    }
501
502#ifdef BADCORNER
503    /* if addressing the lower right corner doesn't work in your curses */
504    refs[last_y][last_x] = 1;
505#endif /* BADCORNER */
506
507    for (n = number, w = &worm[0]; --n >= 0; w++) {
508	w->attrs = flavor[n % SIZEOF(flavor)];
509	w->orientation = 0;
510	w->head = 0;
511
512	if (!(ip = typeMalloc(short, (length + 1)))) {
513	    fprintf(stderr, "%s: out of memory\n", *argv);
514	    ExitProgram(EXIT_FAILURE);
515	}
516	w->xpos = ip;
517	for (x = length; --x >= 0;)
518	    *ip++ = -1;
519	if (!(ip = typeMalloc(short, (length + 1)))) {
520	    fprintf(stderr, "%s: out of memory\n", *argv);
521	    ExitProgram(EXIT_FAILURE);
522	}
523	w->ypos = ip;
524	for (y = length; --y >= 0;)
525	    *ip++ = -1;
526    }
527    if (field) {
528	const char *p;
529	p = field;
530	for (y = last_y; --y >= 0;) {
531	    for (x = COLS; --x >= 0;) {
532		addch((chtype) (*p++));
533		if (!*p)
534		    p = field;
535	    }
536	}
537    }
538    USING_WINDOW(stdscr, wrefresh);
539    nodelay(stdscr, TRUE);
540
541    while (!done) {
542	int ch;
543
544	++sequence;
545	if ((ch = get_input()) > 0) {
546#ifdef TRACE
547	    if (trace_start || trace_end) {
548		if (generation == trace_start) {
549		    trace(TRACE_CALLS);
550		    get_input();
551		} else if (generation == trace_end) {
552		    trace(0);
553		    get_input();
554		}
555
556		generation++;
557	    }
558#endif
559
560#ifdef KEY_RESIZE
561	    if (ch == KEY_RESIZE) {
562		USING_WINDOW(stdscr, update_refs);
563	    }
564#endif
565
566	    /*
567	     * Make it simple to put this into single-step mode, or resume
568	     * normal operation -T.Dickey
569	     */
570	    if (ch == 'q') {
571		quitting = TRUE;
572		done = TRUE;
573		continue;
574	    } else if (ch == 's') {
575		nodelay(stdscr, FALSE);
576	    } else if (ch == ' ') {
577		nodelay(stdscr, TRUE);
578	    }
579	}
580
581	done = draw_all_worms();
582	napms(10);
583	USING_WINDOW(stdscr, wrefresh);
584    }
585
586    Trace(("Cleanup"));
587    cleanup();
588#ifdef NO_LEAKS
589    for (y = 0; y < LINES; y++) {
590	free(refs[y]);
591    }
592    free(refs);
593    for (n = number, w = &worm[0]; --n >= 0; w++) {
594	free(w->xpos);
595	free(w->ypos);
596    }
597#endif
598#ifdef USE_PTHREADS
599    /*
600     * Do this just in case one of the threads did not really exit.
601     */
602    Trace(("join all threads"));
603    for (n = 0; n < number; n++) {
604	pthread_join(worm[n].thread, NULL);
605    }
606#endif
607    ExitProgram(EXIT_SUCCESS);
608}
609