ncurses 6.0 - patch 20171118
[ncurses.git] / test / worm.c
1 /****************************************************************************
2  * Copyright (c) 1998-2016,2017 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.76 2017/11/18 22:41:08 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 #ifdef KEY_RESIZE
201 static void
202 failed(const char *s)
203 {
204     perror(s);
205     exit_curses();
206     ExitProgram(EXIT_FAILURE);
207 }
208 #endif
209
210 static void
211 cleanup(void)
212 {
213     USING_WINDOW(stdscr, wrefresh);
214     exit_curses();
215 }
216
217 static void
218 onsig(int sig GCC_UNUSED)
219 {
220     cleanup();
221     ExitProgram(EXIT_FAILURE);
222 }
223
224 static double
225 ranf(void)
226 {
227     long r = (rand() & 077777);
228     return ((double) r / 32768.);
229 }
230
231 static int
232 draw_worm(WINDOW *win, void *data)
233 {
234     WORM *w = (WORM *) data;
235     const struct options *op;
236     unsigned mask = (unsigned) (~(1 << (w - worm)));
237     chtype attrs = w->attrs | ((mask & pending) ? A_REVERSE : 0);
238
239     int x;
240     int y;
241     int h;
242
243     bool done = FALSE;
244
245     if ((x = w->xpos[h = w->head]) < 0) {
246         wmove(win, y = w->ypos[h] = last_y, x = w->xpos[h] = 0);
247         waddch(win, attrs);
248         refs[y][x]++;
249     } else {
250         y = w->ypos[h];
251     }
252
253     if (x > last_x)
254         x = last_x;
255     if (y > last_y)
256         y = last_y;
257
258     if (++h == length)
259         h = 0;
260
261     if (w->xpos[w->head = h] >= 0) {
262         int x1, y1;
263         x1 = w->xpos[h];
264         y1 = w->ypos[h];
265         if (y1 < LINES
266             && x1 < COLS
267             && --refs[y1][x1] == 0) {
268             wmove(win, y1, x1);
269             waddch(win, trail);
270         }
271     }
272
273     op = &(x == 0
274            ? (y == 0
275               ? upleft
276               : (y == last_y
277                  ? lowleft
278                  : left))
279            : (x == last_x
280               ? (y == 0
281                  ? upright
282                  : (y == last_y
283                     ? lowright
284                     : right))
285               : (y == 0
286                  ? upper
287                  : (y == last_y
288                     ? lower
289                     : normal))))[w->orientation];
290
291     switch (op->nopts) {
292     case 0:
293         done = TRUE;
294         Trace(("done - draw_worm"));
295         break;
296     case 1:
297         w->orientation = op->opts[0];
298         break;
299     default:
300         w->orientation = op->opts[(int) (ranf() * (double) op->nopts)];
301         break;
302     }
303
304     if (!done) {
305         x += xinc[w->orientation];
306         y += yinc[w->orientation];
307         wmove(win, y, x);
308
309         if (y < 0)
310             y = 0;
311         waddch(win, attrs);
312
313         w->ypos[h] = y;
314         w->xpos[h] = x;
315         refs[y][x]++;
316     }
317
318     return done;
319 }
320
321 #ifdef USE_PTHREADS
322 static bool
323 quit_worm(int bitnum)
324 {
325     pending = (pending | (unsigned) (1 << bitnum));
326     napms(10);                  /* let the other thread(s) have a chance */
327     pending = (pending & (unsigned) ~(1 << bitnum));
328     return quitting;
329 }
330
331 static void *
332 start_worm(void *arg)
333 {
334     unsigned long compare = 0;
335     Trace(("start_worm"));
336     while (!quit_worm((int) (((struct worm *) arg) - worm))) {
337         while (compare < sequence) {
338             ++compare;
339 #if HAVE_USE_WINDOW
340             use_window(stdscr, draw_worm, arg);
341 #else
342             draw_worm(stdscr, arg);
343 #endif
344         }
345     }
346     Trace(("...start_worm (done)"));
347     return NULL;
348 }
349 #endif
350
351 static bool
352 draw_all_worms(void)
353 {
354     bool done = FALSE;
355     int n;
356     struct worm *w;
357
358 #ifdef USE_PTHREADS
359     static bool first = TRUE;
360     if (first) {
361         first = FALSE;
362         for (n = 0, w = &worm[0]; n < number; n++, w++) {
363             (void) pthread_create(&(w->thread), NULL, start_worm, w);
364         }
365     }
366 #else
367     for (n = 0, w = &worm[0]; n < number; n++, w++) {
368         if (
369 #if HAVE_USE_WINDOW
370                USING_WINDOW2(stdscr, draw_worm, w)
371 #else
372                draw_worm(stdscr, w)
373 #endif
374             )
375             done = TRUE;
376     }
377 #endif
378     return done;
379 }
380
381 static int
382 get_input(void)
383 {
384     int ch;
385     ch = USING_WINDOW(stdscr, wgetch);
386     return ch;
387 }
388
389 #ifdef KEY_RESIZE
390 static int
391 update_refs(WINDOW *win)
392 {
393     int x, y;
394
395     (void) win;
396     if (last_x != COLS - 1) {
397         for (y = 0; y <= last_y; y++) {
398             refs[y] = typeRealloc(int, (size_t) COLS, refs[y]);
399             if (!refs[y])
400                 failed("update_refs");
401             for (x = last_x + 1; x < COLS; x++)
402                 refs[y][x] = 0;
403         }
404         last_x = COLS - 1;
405     }
406     if (last_y != LINES - 1) {
407         for (y = LINES; y <= last_y; y++)
408             free(refs[y]);
409         max_refs = LINES;
410         refs = typeRealloc(int *, (size_t) LINES, refs);
411         for (y = last_y + 1; y < LINES; y++) {
412             refs[y] = typeMalloc(int, (size_t) COLS);
413             if (!refs[y])
414                 failed("update_refs");
415             for (x = 0; x < COLS; x++)
416                 refs[y][x] = 0;
417         }
418         last_y = LINES - 1;
419     }
420     return OK;
421 }
422 #endif
423
424 static void
425 usage(void)
426 {
427     static const char *msg[] =
428     {
429         "Usage: worm [options]"
430         ,""
431         ,"Options:"
432 #if HAVE_USE_DEFAULT_COLORS
433         ," -d       invoke use_default_colors"
434 #endif
435         ," -f       fill screen with copies of \"WORM\" at start"
436         ," -l <n>   set length of worms"
437         ," -n <n>   set number of worms"
438         ," -t       leave trail of \".\""
439 #ifdef TRACE
440         ," -T <start>,<end> set trace interval"
441         ," -N       suppress cursor-movement optimization"
442 #endif
443     };
444     size_t n;
445
446     for (n = 0; n < SIZEOF(msg); n++)
447         fprintf(stderr, "%s\n", msg[n]);
448
449     ExitProgram(EXIT_FAILURE);
450 }
451
452 int
453 main(int argc, char *argv[])
454 {
455     int ch;
456     int x, y;
457     int n;
458     struct worm *w;
459     int *ip;
460     bool done = FALSE;
461 #if HAVE_USE_DEFAULT_COLORS
462     bool opt_d = FALSE;
463 #endif
464
465     setlocale(LC_ALL, "");
466
467     while ((ch = getopt(argc, argv, "dfl:n:tT:N")) != -1) {
468         switch (ch) {
469 #if HAVE_USE_DEFAULT_COLORS
470         case 'd':
471             opt_d = TRUE;
472             break;
473 #endif
474         case 'f':
475             field = "WORM";
476             break;
477         case 'l':
478             if ((length = atoi(optarg)) < 2 || length > MAX_LENGTH) {
479                 fprintf(stderr, "%s: Invalid length\n", *argv);
480                 usage();
481             }
482             break;
483         case 'n':
484             if ((number = atoi(optarg)) < 1 || number > MAX_WORMS) {
485                 fprintf(stderr, "%s: Invalid number of worms\n", *argv);
486                 usage();
487             }
488             break;
489         case 't':
490             trail = '.';
491             break;
492 #ifdef TRACE
493         case 'T':
494             if (sscanf(optarg, "%d,%d", &trace_start, &trace_end) != 2)
495                 usage();
496             break;
497         case 'N':
498             _nc_optimize_enable ^= OPTIMIZE_ALL;        /* declared by ncurses */
499             break;
500 #endif /* TRACE */
501         default:
502             usage();
503             /* NOTREACHED */
504         }
505     }
506     if (optind < argc)
507         usage();
508
509     signal(SIGINT, onsig);
510     initscr();
511     noecho();
512     cbreak();
513     nonl();
514
515     curs_set(0);
516
517     last_y = LINES - 1;
518     last_x = COLS - 1;
519
520 #ifdef A_COLOR
521     if (has_colors()) {
522         int bg = COLOR_BLACK;
523         start_color();
524 #if HAVE_USE_DEFAULT_COLORS
525         if (opt_d && (use_default_colors() == OK))
526             bg = -1;
527 #endif
528
529 #define SET_COLOR(num, fg) \
530             init_pair(num+1, (short) fg, (short) bg); \
531             flavor[num] |= (chtype) COLOR_PAIR(num+1) | A_BOLD
532
533         SET_COLOR(0, COLOR_GREEN);
534         SET_COLOR(1, COLOR_RED);
535         SET_COLOR(2, COLOR_CYAN);
536         SET_COLOR(3, COLOR_WHITE);
537         SET_COLOR(4, COLOR_MAGENTA);
538         SET_COLOR(5, COLOR_BLUE);
539         SET_COLOR(6, COLOR_YELLOW);
540     }
541 #endif /* A_COLOR */
542
543     max_refs = LINES;
544     refs = typeMalloc(int *, (size_t) max_refs);
545     for (y = 0; y < max_refs; y++) {
546         refs[y] = typeMalloc(int, (size_t) COLS);
547         for (x = 0; x < COLS; x++) {
548             refs[y][x] = 0;
549         }
550     }
551
552 #ifdef BADCORNER
553     /* if addressing the lower right corner doesn't work in your curses */
554     refs[last_y][last_x] = 1;
555 #endif /* BADCORNER */
556
557     for (n = number, w = &worm[0]; --n >= 0; w++) {
558         w->attrs = flavor[(unsigned) n % SIZEOF(flavor)];
559         w->orientation = 0;
560         w->head = 0;
561
562         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
563             fprintf(stderr, "%s: out of memory\n", *argv);
564             ExitProgram(EXIT_FAILURE);
565         }
566         w->xpos = ip;
567         for (x = length; --x >= 0;)
568             *ip++ = -1;
569         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
570             fprintf(stderr, "%s: out of memory\n", *argv);
571             ExitProgram(EXIT_FAILURE);
572         }
573         w->ypos = ip;
574         for (y = length; --y >= 0;)
575             *ip++ = -1;
576     }
577     if (field) {
578         const char *p;
579         p = field;
580         for (y = last_y; --y >= 0;) {
581             for (x = COLS; --x >= 0;) {
582                 addch((chtype) (*p++));
583                 if (!*p)
584                     p = field;
585             }
586         }
587     }
588     USING_WINDOW(stdscr, wrefresh);
589     nodelay(stdscr, TRUE);
590
591     while (!done) {
592         ++sequence;
593         if ((ch = get_input()) > 0) {
594 #ifdef TRACE
595             if (trace_start || trace_end) {
596                 if (generation == trace_start) {
597                     trace(TRACE_CALLS);
598                     get_input();
599                 } else if (generation == trace_end) {
600                     trace(0);
601                     get_input();
602                 }
603
604                 generation++;
605             }
606 #endif
607
608 #ifdef KEY_RESIZE
609             if (ch == KEY_RESIZE) {
610                 USING_WINDOW(stdscr, update_refs);
611             }
612 #endif
613
614             /*
615              * Make it simple to put this into single-step mode, or resume
616              * normal operation -T.Dickey
617              */
618             if (ch == 'q') {
619                 quitting = TRUE;
620                 done = TRUE;
621                 Trace(("done - quitting"));
622                 continue;
623             } else if (ch == 's') {
624                 nodelay(stdscr, FALSE);
625             } else if (ch == ' ') {
626                 nodelay(stdscr, TRUE);
627             }
628         }
629
630         done = draw_all_worms();
631         napms(10);
632         USING_WINDOW(stdscr, wrefresh);
633     }
634
635     Trace(("Cleanup"));
636     cleanup();
637 #if NO_LEAKS
638     for (y = 0; y < max_refs; y++) {
639         free(refs[y]);
640     }
641     free(refs);
642     for (n = number, w = &worm[0]; --n >= 0; w++) {
643         free(w->xpos);
644         free(w->ypos);
645     }
646 #endif
647 #ifdef USE_PTHREADS
648     /*
649      * Do this just in case one of the threads did not really exit.
650      */
651     Trace(("join all threads"));
652     for (n = 0; n < number; n++) {
653         pthread_join(worm[n].thread, NULL);
654     }
655 #endif
656     ExitProgram(EXIT_SUCCESS);
657 }