ncurses 5.9 - patch 20111022
[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.60 2010/11/13 20:21:21 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 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 double
220 ranf(void)
221 {
222     long r = (rand() & 077777);
223     return ((double) 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 = (unsigned) (~(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() * (double) 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 #if HAVE_USE_WINDOW
334             use_window(stdscr, draw_worm, arg);
335 #else
336             draw_worm(stdscr, arg);
337 #endif
338         }
339     }
340     Trace(("...start_worm (done)"));
341     return NULL;
342 }
343 #endif
344
345 static bool
346 draw_all_worms(void)
347 {
348     bool done = FALSE;
349     int n;
350     struct worm *w;
351
352 #ifdef USE_PTHREADS
353     static bool first = TRUE;
354     if (first) {
355         first = FALSE;
356         for (n = 0, w = &worm[0]; n < number; n++, w++) {
357             int rc;
358             rc = pthread_create(&(w->thread), NULL, start_worm, w);
359         }
360     }
361 #else
362     for (n = 0, w = &worm[0]; n < number; n++, w++) {
363         if (
364 #if HAVE_USE_WINDOW
365                USING_WINDOW2(stdscr, draw_worm, w)
366 #else
367                draw_worm(stdscr, w)
368 #endif
369             )
370             done = TRUE;
371     }
372 #endif
373     return done;
374 }
375
376 static int
377 get_input(void)
378 {
379     int ch;
380     ch = USING_WINDOW(stdscr, wgetch);
381     return ch;
382 }
383
384 #ifdef KEY_RESIZE
385 static int
386 update_refs(WINDOW *win)
387 {
388     int x, y;
389
390     (void) win;
391     if (last_x != COLS - 1) {
392         for (y = 0; y <= last_y; y++) {
393             refs[y] = typeRealloc(int, (size_t) COLS, refs[y]);
394             for (x = last_x + 1; x < COLS; x++)
395                 refs[y][x] = 0;
396         }
397         last_x = COLS - 1;
398     }
399     if (last_y != LINES - 1) {
400         for (y = LINES; y <= last_y; y++)
401             free(refs[y]);
402         refs = typeRealloc(int *, (size_t) LINES, refs);
403         for (y = last_y + 1; y < LINES; y++) {
404             refs[y] = typeMalloc(int, (size_t) COLS);
405             for (x = 0; x < COLS; x++)
406                 refs[y][x] = 0;
407         }
408         last_y = LINES - 1;
409     }
410     return OK;
411 }
412 #endif
413
414 int
415 main(int argc, char *argv[])
416 {
417     int x, y;
418     int n;
419     struct worm *w;
420     int *ip;
421     bool done = FALSE;
422
423     setlocale(LC_ALL, "");
424
425     for (x = 1; x < argc; x++) {
426         char *p;
427         p = argv[x];
428         if (*p == '-')
429             p++;
430         switch (*p) {
431         case 'f':
432             field = "WORM";
433             break;
434         case 'l':
435             if (++x == argc)
436                 goto usage;
437             if ((length = atoi(argv[x])) < 2 || length > MAX_LENGTH) {
438                 fprintf(stderr, "%s: Invalid length\n", *argv);
439                 ExitProgram(EXIT_FAILURE);
440             }
441             break;
442         case 'n':
443             if (++x == argc)
444                 goto usage;
445             if ((number = atoi(argv[x])) < 1 || number > MAX_WORMS) {
446                 fprintf(stderr, "%s: Invalid number of worms\n", *argv);
447                 ExitProgram(EXIT_FAILURE);
448             }
449             break;
450         case 't':
451             trail = '.';
452             break;
453 #ifdef TRACE
454         case 'T':
455             trace_start = atoi(argv[++x]);
456             trace_end = atoi(argv[++x]);
457             break;
458         case 'N':
459             _nc_optimize_enable ^= OPTIMIZE_ALL;        /* declared by ncurses */
460             break;
461 #endif /* TRACE */
462         default:
463           usage:
464             fprintf(stderr,
465                     "usage: %s [-field] [-length #] [-number #] [-trail]\n", *argv);
466             ExitProgram(EXIT_FAILURE);
467         }
468     }
469
470     signal(SIGINT, onsig);
471     initscr();
472     noecho();
473     cbreak();
474     nonl();
475
476     curs_set(0);
477
478     last_y = LINES - 1;
479     last_x = COLS - 1;
480
481 #ifdef A_COLOR
482     if (has_colors()) {
483         int bg = COLOR_BLACK;
484         start_color();
485 #if HAVE_USE_DEFAULT_COLORS
486         if (use_default_colors() == OK)
487             bg = -1;
488 #endif
489
490 #define SET_COLOR(num, fg) \
491             init_pair(num+1, (short) fg, (short) bg); \
492             flavor[num] |= COLOR_PAIR(num+1) | A_BOLD
493
494         SET_COLOR(0, COLOR_GREEN);
495         SET_COLOR(1, COLOR_RED);
496         SET_COLOR(2, COLOR_CYAN);
497         SET_COLOR(3, COLOR_WHITE);
498         SET_COLOR(4, COLOR_MAGENTA);
499         SET_COLOR(5, COLOR_BLUE);
500         SET_COLOR(6, COLOR_YELLOW);
501     }
502 #endif /* A_COLOR */
503
504     refs = typeMalloc(int *, (size_t) LINES);
505     for (y = 0; y < LINES; y++) {
506         refs[y] = typeMalloc(int, (size_t) COLS);
507         for (x = 0; x < COLS; x++) {
508             refs[y][x] = 0;
509         }
510     }
511
512 #ifdef BADCORNER
513     /* if addressing the lower right corner doesn't work in your curses */
514     refs[last_y][last_x] = 1;
515 #endif /* BADCORNER */
516
517     for (n = number, w = &worm[0]; --n >= 0; w++) {
518         w->attrs = flavor[(unsigned) n % SIZEOF(flavor)];
519         w->orientation = 0;
520         w->head = 0;
521
522         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
523             fprintf(stderr, "%s: out of memory\n", *argv);
524             ExitProgram(EXIT_FAILURE);
525         }
526         w->xpos = ip;
527         for (x = length; --x >= 0;)
528             *ip++ = -1;
529         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
530             fprintf(stderr, "%s: out of memory\n", *argv);
531             ExitProgram(EXIT_FAILURE);
532         }
533         w->ypos = ip;
534         for (y = length; --y >= 0;)
535             *ip++ = -1;
536     }
537     if (field) {
538         const char *p;
539         p = field;
540         for (y = last_y; --y >= 0;) {
541             for (x = COLS; --x >= 0;) {
542                 addch((chtype) (*p++));
543                 if (!*p)
544                     p = field;
545             }
546         }
547     }
548     USING_WINDOW(stdscr, wrefresh);
549     nodelay(stdscr, TRUE);
550
551     while (!done) {
552         int ch;
553
554         ++sequence;
555         if ((ch = get_input()) > 0) {
556 #ifdef TRACE
557             if (trace_start || trace_end) {
558                 if (generation == trace_start) {
559                     trace(TRACE_CALLS);
560                     get_input();
561                 } else if (generation == trace_end) {
562                     trace(0);
563                     get_input();
564                 }
565
566                 generation++;
567             }
568 #endif
569
570 #ifdef KEY_RESIZE
571             if (ch == KEY_RESIZE) {
572                 USING_WINDOW(stdscr, update_refs);
573             }
574 #endif
575
576             /*
577              * Make it simple to put this into single-step mode, or resume
578              * normal operation -T.Dickey
579              */
580             if (ch == 'q') {
581                 quitting = TRUE;
582                 done = TRUE;
583                 continue;
584             } else if (ch == 's') {
585                 nodelay(stdscr, FALSE);
586             } else if (ch == ' ') {
587                 nodelay(stdscr, TRUE);
588             }
589         }
590
591         done = draw_all_worms();
592         napms(10);
593         USING_WINDOW(stdscr, wrefresh);
594     }
595
596     Trace(("Cleanup"));
597     cleanup();
598 #ifdef NO_LEAKS
599     for (y = 0; y < LINES; y++) {
600         free(refs[y]);
601     }
602     free(refs);
603     for (n = number, w = &worm[0]; --n >= 0; w++) {
604         free(w->xpos);
605         free(w->ypos);
606     }
607 #endif
608 #ifdef USE_PTHREADS
609     /*
610      * Do this just in case one of the threads did not really exit.
611      */
612     Trace(("join all threads"));
613     for (n = 0; n < number; n++) {
614         pthread_join(worm[n].thread, NULL);
615     }
616 #endif
617     ExitProgram(EXIT_SUCCESS);
618 }