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