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