ncurses 5.9 - patch 20121208
[ncurses.git] / test / worm.c
1 /****************************************************************************
2  * Copyright (c) 1998-2008,2012 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.61 2012/10/27 19:37:17 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 RETSIGTYPE
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             int rc;
366             rc = pthread_create(&(w->thread), NULL, start_worm, w);
367         }
368     }
369 #else
370     for (n = 0, w = &worm[0]; n < number; n++, w++) {
371         if (
372 #if HAVE_USE_WINDOW
373                USING_WINDOW2(stdscr, draw_worm, w)
374 #else
375                draw_worm(stdscr, w)
376 #endif
377             )
378             done = TRUE;
379     }
380 #endif
381     return done;
382 }
383
384 static int
385 get_input(void)
386 {
387     int ch;
388     ch = USING_WINDOW(stdscr, wgetch);
389     return ch;
390 }
391
392 #ifdef KEY_RESIZE
393 static int
394 update_refs(WINDOW *win)
395 {
396     int x, y;
397
398     (void) win;
399     if (last_x != COLS - 1) {
400         for (y = 0; y <= last_y; y++) {
401             refs[y] = typeRealloc(int, (size_t) COLS, refs[y]);
402             if (!refs[y])
403                 failed("update_refs");
404             for (x = last_x + 1; x < COLS; x++)
405                 refs[y][x] = 0;
406         }
407         last_x = COLS - 1;
408     }
409     if (last_y != LINES - 1) {
410         for (y = LINES; y <= last_y; y++)
411             free(refs[y]);
412         refs = typeRealloc(int *, (size_t) LINES, refs);
413         for (y = last_y + 1; y < LINES; y++) {
414             refs[y] = typeMalloc(int, (size_t) COLS);
415             if (!refs[y])
416                 failed("update_refs");
417             for (x = 0; x < COLS; x++)
418                 refs[y][x] = 0;
419         }
420         last_y = LINES - 1;
421     }
422     return OK;
423 }
424 #endif
425
426 int
427 main(int argc, char *argv[])
428 {
429     int x, y;
430     int n;
431     struct worm *w;
432     int *ip;
433     bool done = FALSE;
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] |= 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     refs = typeMalloc(int *, (size_t) LINES);
517     for (y = 0; y < LINES; y++) {
518         refs[y] = typeMalloc(int, (size_t) COLS);
519         for (x = 0; x < COLS; x++) {
520             refs[y][x] = 0;
521         }
522     }
523
524 #ifdef BADCORNER
525     /* if addressing the lower right corner doesn't work in your curses */
526     refs[last_y][last_x] = 1;
527 #endif /* BADCORNER */
528
529     for (n = number, w = &worm[0]; --n >= 0; w++) {
530         w->attrs = flavor[(unsigned) n % SIZEOF(flavor)];
531         w->orientation = 0;
532         w->head = 0;
533
534         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
535             fprintf(stderr, "%s: out of memory\n", *argv);
536             ExitProgram(EXIT_FAILURE);
537         }
538         w->xpos = ip;
539         for (x = length; --x >= 0;)
540             *ip++ = -1;
541         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
542             fprintf(stderr, "%s: out of memory\n", *argv);
543             ExitProgram(EXIT_FAILURE);
544         }
545         w->ypos = ip;
546         for (y = length; --y >= 0;)
547             *ip++ = -1;
548     }
549     if (field) {
550         const char *p;
551         p = field;
552         for (y = last_y; --y >= 0;) {
553             for (x = COLS; --x >= 0;) {
554                 addch((chtype) (*p++));
555                 if (!*p)
556                     p = field;
557             }
558         }
559     }
560     USING_WINDOW(stdscr, wrefresh);
561     nodelay(stdscr, TRUE);
562
563     while (!done) {
564         int ch;
565
566         ++sequence;
567         if ((ch = get_input()) > 0) {
568 #ifdef TRACE
569             if (trace_start || trace_end) {
570                 if (generation == trace_start) {
571                     trace(TRACE_CALLS);
572                     get_input();
573                 } else if (generation == trace_end) {
574                     trace(0);
575                     get_input();
576                 }
577
578                 generation++;
579             }
580 #endif
581
582 #ifdef KEY_RESIZE
583             if (ch == KEY_RESIZE) {
584                 USING_WINDOW(stdscr, update_refs);
585             }
586 #endif
587
588             /*
589              * Make it simple to put this into single-step mode, or resume
590              * normal operation -T.Dickey
591              */
592             if (ch == 'q') {
593                 quitting = TRUE;
594                 done = TRUE;
595                 continue;
596             } else if (ch == 's') {
597                 nodelay(stdscr, FALSE);
598             } else if (ch == ' ') {
599                 nodelay(stdscr, TRUE);
600             }
601         }
602
603         done = draw_all_worms();
604         napms(10);
605         USING_WINDOW(stdscr, wrefresh);
606     }
607
608     Trace(("Cleanup"));
609     cleanup();
610 #ifdef NO_LEAKS
611     for (y = 0; y < LINES; y++) {
612         free(refs[y]);
613     }
614     free(refs);
615     for (n = number, w = &worm[0]; --n >= 0; w++) {
616         free(w->xpos);
617         free(w->ypos);
618     }
619 #endif
620 #ifdef USE_PTHREADS
621     /*
622      * Do this just in case one of the threads did not really exit.
623      */
624     Trace(("join all threads"));
625     for (n = 0; n < number; n++) {
626         pthread_join(worm[n].thread, NULL);
627     }
628 #endif
629     ExitProgram(EXIT_SUCCESS);
630 }