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