ncurses 6.2 - patch 20210418
[ncurses.git] / test / worm.c
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
203 safe_wgetch(WINDOW *w, void *data GCC_UNUSED)
204 {
205     return wgetch(w);
206 }
207 static int
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
216 failed(const char *s)
217 {
218     perror(s);
219     stop_curses();
220     ExitProgram(EXIT_FAILURE);
221 }
222 #endif
223
224 static void
225 cleanup(void)
226 {
227     USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
228     stop_curses();
229 }
230
231 static void
232 onsig(int sig GCC_UNUSED)
233 {
234     cleanup();
235     ExitProgram(EXIT_FAILURE);
236 }
237
238 static double
239 ranf(void)
240 {
241     long r = (rand() & 077777);
242     return ((double) r / 32768.);
243 }
244
245 static int
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
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 *
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
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
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
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
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
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 }