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