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