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