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