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