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