c7b04aa814451d0253b79fcb6a61a4dd0b638414
[ncurses.git] / test / worm.c
1 /****************************************************************************
2  * Copyright (c) 1998-2018,2019 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.81 2019/12/14 23:25:29 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     stop_curses();
219     ExitProgram(EXIT_FAILURE);
220 }
221 #endif
222
223 static void
224 cleanup(void)
225 {
226     USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
227     stop_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)
405 {
406     int x, y;
407
408     (void) win;
409     (void) data;
410     if (last_x != COLS - 1) {
411         for (y = 0; y <= last_y; y++) {
412             refs[y] = typeRealloc(int, (size_t) COLS, refs[y]);
413             if (!refs[y])
414                 failed("update_refs");
415             for (x = last_x + 1; x < COLS; x++)
416                 refs[y][x] = 0;
417         }
418         last_x = COLS - 1;
419     }
420     if (last_y != LINES - 1) {
421         for (y = LINES; y <= last_y; y++)
422             free(refs[y]);
423         max_refs = LINES;
424         refs = typeRealloc(int *, (size_t) LINES, refs);
425         for (y = last_y + 1; y < LINES; y++) {
426             refs[y] = typeMalloc(int, (size_t) COLS);
427             if (!refs[y])
428                 failed("update_refs");
429             for (x = 0; x < COLS; x++)
430                 refs[y][x] = 0;
431         }
432         last_y = LINES - 1;
433     }
434     return OK;
435 }
436 #endif
437
438 static void
439 usage(void)
440 {
441     static const char *msg[] =
442     {
443         "Usage: worm [options]"
444         ,""
445         ,"Options:"
446 #if HAVE_USE_DEFAULT_COLORS
447         ," -d       invoke use_default_colors"
448 #endif
449         ," -f       fill screen with copies of \"WORM\" at start"
450         ," -l <n>   set length of worms"
451         ," -n <n>   set number of worms"
452         ," -t       leave trail of \".\""
453 #ifdef TRACE
454         ," -T <start>,<end> set trace interval"
455         ," -N       suppress cursor-movement optimization"
456 #endif
457     };
458     size_t n;
459
460     for (n = 0; n < SIZEOF(msg); n++)
461         fprintf(stderr, "%s\n", msg[n]);
462
463     ExitProgram(EXIT_FAILURE);
464 }
465
466 int
467 main(int argc, char *argv[])
468 {
469     int ch;
470     int x, y;
471     int n;
472     struct worm *w;
473     int *ip;
474     bool done = FALSE;
475 #if HAVE_USE_DEFAULT_COLORS
476     bool opt_d = FALSE;
477 #endif
478
479     setlocale(LC_ALL, "");
480
481     while ((ch = getopt(argc, argv, "dfl:n:tT:N")) != -1) {
482         switch (ch) {
483 #if HAVE_USE_DEFAULT_COLORS
484         case 'd':
485             opt_d = TRUE;
486             break;
487 #endif
488         case 'f':
489             field = "WORM";
490             break;
491         case 'l':
492             if ((length = atoi(optarg)) < 2 || length > MAX_LENGTH) {
493                 fprintf(stderr, "%s: Invalid length\n", *argv);
494                 usage();
495             }
496             break;
497         case 'n':
498             if ((number = atoi(optarg)) < 1 || number > MAX_WORMS) {
499                 fprintf(stderr, "%s: Invalid number of worms\n", *argv);
500                 usage();
501             }
502             break;
503         case 't':
504             trail = '.';
505             break;
506 #ifdef TRACE
507         case 'T':
508             if (sscanf(optarg, "%d,%d", &trace_start, &trace_end) != 2)
509                 usage();
510             break;
511         case 'N':
512             _nc_optimize_enable ^= OPTIMIZE_ALL;        /* declared by ncurses */
513             break;
514 #endif /* TRACE */
515         default:
516             usage();
517             /* NOTREACHED */
518         }
519     }
520     if (optind < argc)
521         usage();
522
523     signal(SIGINT, onsig);
524     initscr();
525     noecho();
526     cbreak();
527     nonl();
528
529     curs_set(0);
530
531     last_y = LINES - 1;
532     last_x = COLS - 1;
533
534 #ifdef A_COLOR
535     if (has_colors()) {
536         int bg = COLOR_BLACK;
537         start_color();
538 #if HAVE_USE_DEFAULT_COLORS
539         if (opt_d && (use_default_colors() == OK))
540             bg = -1;
541 #endif
542
543 #define SET_COLOR(num, fg) \
544             init_pair(num+1, (short) fg, (short) bg); \
545             flavor[num] |= (chtype) COLOR_PAIR(num+1) | A_BOLD
546
547         SET_COLOR(0, COLOR_GREEN);
548         SET_COLOR(1, COLOR_RED);
549         SET_COLOR(2, COLOR_CYAN);
550         SET_COLOR(3, COLOR_WHITE);
551         SET_COLOR(4, COLOR_MAGENTA);
552         SET_COLOR(5, COLOR_BLUE);
553         SET_COLOR(6, COLOR_YELLOW);
554     }
555 #endif /* A_COLOR */
556
557     max_refs = LINES;
558     refs = typeMalloc(int *, (size_t) max_refs);
559     for (y = 0; y < max_refs; y++) {
560         refs[y] = typeMalloc(int, (size_t) COLS);
561         for (x = 0; x < COLS; x++) {
562             refs[y][x] = 0;
563         }
564     }
565
566 #ifdef BADCORNER
567     /* if addressing the lower right corner doesn't work in your curses */
568     refs[last_y][last_x] = 1;
569 #endif /* BADCORNER */
570
571     for (n = number, w = &worm[0]; --n >= 0; w++) {
572         w->attrs = flavor[(unsigned) n % SIZEOF(flavor)];
573         w->orientation = 0;
574         w->head = 0;
575
576         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
577             fprintf(stderr, "%s: out of memory\n", *argv);
578             ExitProgram(EXIT_FAILURE);
579         }
580         w->xpos = ip;
581         for (x = length; --x >= 0;)
582             *ip++ = -1;
583         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
584             fprintf(stderr, "%s: out of memory\n", *argv);
585             ExitProgram(EXIT_FAILURE);
586         }
587         w->ypos = ip;
588         for (y = length; --y >= 0;)
589             *ip++ = -1;
590     }
591     if (field) {
592         const char *p;
593         p = field;
594         for (y = last_y; --y >= 0;) {
595             for (x = COLS; --x >= 0;) {
596                 addch((chtype) (*p++));
597                 if (!*p)
598                     p = field;
599             }
600         }
601     }
602     USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
603     nodelay(stdscr, TRUE);
604
605     while (!done) {
606         ++sequence;
607         if ((ch = get_input()) > 0) {
608 #ifdef TRACE
609             if (trace_start || trace_end) {
610                 if (generation == trace_start) {
611                     curses_trace(TRACE_CALLS);
612                     get_input();
613                 } else if (generation == trace_end) {
614                     curses_trace(0);
615                     get_input();
616                 }
617
618                 generation++;
619             }
620 #endif
621
622 #ifdef KEY_RESIZE
623             if (ch == KEY_RESIZE) {
624                 USING_WINDOW(stdscr, update_refs);
625             }
626 #endif
627
628             /*
629              * Make it simple to put this into single-step mode, or resume
630              * normal operation -T.Dickey
631              */
632             if (ch == 'q') {
633                 quitting = TRUE;
634                 done = TRUE;
635                 Trace(("done - quitting"));
636                 continue;
637             } else if (ch == 's') {
638                 nodelay(stdscr, FALSE);
639             } else if (ch == ' ') {
640                 nodelay(stdscr, TRUE);
641             }
642         }
643
644         done = draw_all_worms();
645         napms(10);
646         USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
647     }
648
649     Trace(("Cleanup"));
650     cleanup();
651 #if NO_LEAKS
652     for (y = 0; y < max_refs; y++) {
653         free(refs[y]);
654     }
655     free(refs);
656     for (n = number, w = &worm[0]; --n >= 0; w++) {
657         free(w->xpos);
658         free(w->ypos);
659     }
660 #endif
661 #ifdef USE_PTHREADS
662     /*
663      * Do this just in case one of the threads did not really exit.
664      */
665     Trace(("join all threads"));
666     for (n = 0; n < number; n++) {
667         pthread_join(worm[n].thread, NULL);
668     }
669 #endif
670     ExitProgram(EXIT_SUCCESS);
671 }