ncurses 5.9 - patch 20130608
[ncurses.git] / test / worm.c
1 /****************************************************************************
2  * Copyright (c) 1998-2012,2013 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.64 2013/04/27 19:50: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 void
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     int max_refs;
435
436     setlocale(LC_ALL, "");
437
438     for (x = 1; x < argc; x++) {
439         char *p;
440         p = argv[x];
441         if (*p == '-')
442             p++;
443         switch (*p) {
444         case 'f':
445             field = "WORM";
446             break;
447         case 'l':
448             if (++x == argc)
449                 goto usage;
450             if ((length = atoi(argv[x])) < 2 || length > MAX_LENGTH) {
451                 fprintf(stderr, "%s: Invalid length\n", *argv);
452                 ExitProgram(EXIT_FAILURE);
453             }
454             break;
455         case 'n':
456             if (++x == argc)
457                 goto usage;
458             if ((number = atoi(argv[x])) < 1 || number > MAX_WORMS) {
459                 fprintf(stderr, "%s: Invalid number of worms\n", *argv);
460                 ExitProgram(EXIT_FAILURE);
461             }
462             break;
463         case 't':
464             trail = '.';
465             break;
466 #ifdef TRACE
467         case 'T':
468             trace_start = atoi(argv[++x]);
469             trace_end = atoi(argv[++x]);
470             break;
471         case 'N':
472             _nc_optimize_enable ^= OPTIMIZE_ALL;        /* declared by ncurses */
473             break;
474 #endif /* TRACE */
475         default:
476           usage:
477             fprintf(stderr,
478                     "usage: %s [-field] [-length #] [-number #] [-trail]\n", *argv);
479             ExitProgram(EXIT_FAILURE);
480         }
481     }
482
483     signal(SIGINT, onsig);
484     initscr();
485     noecho();
486     cbreak();
487     nonl();
488
489     curs_set(0);
490
491     last_y = LINES - 1;
492     last_x = COLS - 1;
493
494 #ifdef A_COLOR
495     if (has_colors()) {
496         int bg = COLOR_BLACK;
497         start_color();
498 #if HAVE_USE_DEFAULT_COLORS
499         if (use_default_colors() == OK)
500             bg = -1;
501 #endif
502
503 #define SET_COLOR(num, fg) \
504             init_pair(num+1, (short) fg, (short) bg); \
505             flavor[num] |= (chtype) COLOR_PAIR(num+1) | A_BOLD
506
507         SET_COLOR(0, COLOR_GREEN);
508         SET_COLOR(1, COLOR_RED);
509         SET_COLOR(2, COLOR_CYAN);
510         SET_COLOR(3, COLOR_WHITE);
511         SET_COLOR(4, COLOR_MAGENTA);
512         SET_COLOR(5, COLOR_BLUE);
513         SET_COLOR(6, COLOR_YELLOW);
514     }
515 #endif /* A_COLOR */
516
517     max_refs = LINES;
518     refs = typeMalloc(int *, (size_t) max_refs);
519     for (y = 0; y < max_refs; y++) {
520         refs[y] = typeMalloc(int, (size_t) COLS);
521         for (x = 0; x < COLS; x++) {
522             refs[y][x] = 0;
523         }
524     }
525
526 #ifdef BADCORNER
527     /* if addressing the lower right corner doesn't work in your curses */
528     refs[last_y][last_x] = 1;
529 #endif /* BADCORNER */
530
531     for (n = number, w = &worm[0]; --n >= 0; w++) {
532         w->attrs = flavor[(unsigned) n % SIZEOF(flavor)];
533         w->orientation = 0;
534         w->head = 0;
535
536         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
537             fprintf(stderr, "%s: out of memory\n", *argv);
538             ExitProgram(EXIT_FAILURE);
539         }
540         w->xpos = ip;
541         for (x = length; --x >= 0;)
542             *ip++ = -1;
543         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
544             fprintf(stderr, "%s: out of memory\n", *argv);
545             ExitProgram(EXIT_FAILURE);
546         }
547         w->ypos = ip;
548         for (y = length; --y >= 0;)
549             *ip++ = -1;
550     }
551     if (field) {
552         const char *p;
553         p = field;
554         for (y = last_y; --y >= 0;) {
555             for (x = COLS; --x >= 0;) {
556                 addch((chtype) (*p++));
557                 if (!*p)
558                     p = field;
559             }
560         }
561     }
562     USING_WINDOW(stdscr, wrefresh);
563     nodelay(stdscr, TRUE);
564
565     while (!done) {
566         int ch;
567
568         ++sequence;
569         if ((ch = get_input()) > 0) {
570 #ifdef TRACE
571             if (trace_start || trace_end) {
572                 if (generation == trace_start) {
573                     trace(TRACE_CALLS);
574                     get_input();
575                 } else if (generation == trace_end) {
576                     trace(0);
577                     get_input();
578                 }
579
580                 generation++;
581             }
582 #endif
583
584 #ifdef KEY_RESIZE
585             if (ch == KEY_RESIZE) {
586                 USING_WINDOW(stdscr, update_refs);
587             }
588 #endif
589
590             /*
591              * Make it simple to put this into single-step mode, or resume
592              * normal operation -T.Dickey
593              */
594             if (ch == 'q') {
595                 quitting = TRUE;
596                 done = TRUE;
597                 continue;
598             } else if (ch == 's') {
599                 nodelay(stdscr, FALSE);
600             } else if (ch == ' ') {
601                 nodelay(stdscr, TRUE);
602             }
603         }
604
605         done = draw_all_worms();
606         napms(10);
607         USING_WINDOW(stdscr, wrefresh);
608     }
609
610     Trace(("Cleanup"));
611     cleanup();
612 #ifdef NO_LEAKS
613     for (y = 0; y < max_refs; y++) {
614         free(refs[y]);
615     }
616     free(refs);
617     for (n = number, w = &worm[0]; --n >= 0; w++) {
618         free(w->xpos);
619         free(w->ypos);
620     }
621 #endif
622 #ifdef USE_PTHREADS
623     /*
624      * Do this just in case one of the threads did not really exit.
625      */
626     Trace(("join all threads"));
627     for (n = 0; n < number; n++) {
628         pthread_join(worm[n].thread, NULL);
629     }
630 #endif
631     ExitProgram(EXIT_SUCCESS);
632 }