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