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