]> ncurses.scripts.mit.edu Git - ncurses.git/blob - test/worm.c
ab1eb9faf66b0ee5218adb0d7dad948ac0be545c
[ncurses.git] / test / worm.c
1 /****************************************************************************
2  * Copyright 2018-2020,2022 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 /*
30
31          @@@        @@@    @@@@@@@@@@     @@@@@@@@@@@    @@@@@@@@@@@@
32          @@@        @@@   @@@@@@@@@@@@    @@@@@@@@@@@@   @@@@@@@@@@@@@
33          @@@        @@@  @@@@      @@@@   @@@@           @@@@ @@@  @@@@
34          @@@   @@   @@@  @@@        @@@   @@@            @@@  @@@   @@@
35          @@@  @@@@  @@@  @@@        @@@   @@@            @@@  @@@   @@@
36          @@@@ @@@@ @@@@  @@@        @@@   @@@            @@@  @@@   @@@
37           @@@@@@@@@@@@   @@@@      @@@@   @@@            @@@  @@@   @@@
38            @@@@  @@@@     @@@@@@@@@@@@    @@@            @@@  @@@   @@@
39             @@    @@       @@@@@@@@@@     @@@            @@@  @@@   @@@
40
41                                  Eric P. Scott
42                           Caltech High Energy Physics
43                                  October, 1980
44
45                 Hacks to turn this into a test frame for cursor movement:
46                         Eric S. Raymond <esr@snark.thyrsus.com>
47                                 January, 1995
48
49                 July 1995 (esr): worms is now in living color! :-)
50
51   This program makes a good torture-test for the ncurses cursor-optimization
52   code.  You can use -T to set the worm move interval over which movement
53   traces will be dumped.  The program stops and waits for one character of
54   input at the beginning and end of the interval.
55
56   $Id: worm.c,v 1.83 2022/07/09 20:51:25 tom Exp $
57 */
58
59 #include <test.priv.h>
60
61 #ifndef NCURSES_VERSION
62 #undef TRACE
63 #endif
64
65 #ifdef USE_PTHREADS
66 #include <pthread.h>
67 #endif
68
69 WANT_USE_WINDOW();
70
71 #define MAX_WORMS       40
72 #define MAX_LENGTH      1024
73
74 static chtype flavor[] =
75 {
76     'O', '*', '#', '$', '%', '0', '@',
77 };
78 static const int xinc[] =
79 {
80     1, 1, 1, 0, -1, -1, -1, 0
81 }, yinc[] =
82 {
83     -1, 0, 1, 1, 1, 0, -1, -1
84 };
85
86 typedef struct worm {
87     int orientation;
88     int head;
89     int *xpos;
90     int *ypos;
91     chtype attrs;
92 #ifdef USE_PTHREADS
93     pthread_t thread;
94 #endif
95 } WORM;
96
97 static unsigned long sequence = 0;
98 static bool quitting = FALSE;
99
100 static WORM worm[MAX_WORMS];
101 static int max_refs;
102 static int **refs;
103 static int last_x, last_y;
104
105 static const char *field;
106 static int length = 16, number = 3;
107 static chtype trail = ' ';
108
109 static unsigned pending;
110 #ifdef TRACE
111 static int generation, trace_start, trace_end;
112 #endif /* TRACE */
113 /* *INDENT-OFF* */
114 static const struct options {
115     int nopts;
116     int opts[3];
117 } normal[8]={
118     { 3, { 7, 0, 1 } },
119     { 3, { 0, 1, 2 } },
120     { 3, { 1, 2, 3 } },
121     { 3, { 2, 3, 4 } },
122     { 3, { 3, 4, 5 } },
123     { 3, { 4, 5, 6 } },
124     { 3, { 5, 6, 7 } },
125     { 3, { 6, 7, 0 } }
126 }, upper[8]={
127     { 1, { 1, 0, 0 } },
128     { 2, { 1, 2, 0 } },
129     { 0, { 0, 0, 0 } },
130     { 0, { 0, 0, 0 } },
131     { 0, { 0, 0, 0 } },
132     { 2, { 4, 5, 0 } },
133     { 1, { 5, 0, 0 } },
134     { 2, { 1, 5, 0 } }
135 }, left[8]={
136     { 0, { 0, 0, 0 } },
137     { 0, { 0, 0, 0 } },
138     { 0, { 0, 0, 0 } },
139     { 2, { 2, 3, 0 } },
140     { 1, { 3, 0, 0 } },
141     { 2, { 3, 7, 0 } },
142     { 1, { 7, 0, 0 } },
143     { 2, { 7, 0, 0 } }
144 }, right[8]={
145     { 1, { 7, 0, 0 } },
146     { 2, { 3, 7, 0 } },
147     { 1, { 3, 0, 0 } },
148     { 2, { 3, 4, 0 } },
149     { 0, { 0, 0, 0 } },
150     { 0, { 0, 0, 0 } },
151     { 0, { 0, 0, 0 } },
152     { 2, { 6, 7, 0 } }
153 }, lower[8]={
154     { 0, { 0, 0, 0 } },
155     { 2, { 0, 1, 0 } },
156     { 1, { 1, 0, 0 } },
157     { 2, { 1, 5, 0 } },
158     { 1, { 5, 0, 0 } },
159     { 2, { 5, 6, 0 } },
160     { 0, { 0, 0, 0 } },
161     { 0, { 0, 0, 0 } }
162 }, upleft[8]={
163     { 0, { 0, 0, 0 } },
164     { 0, { 0, 0, 0 } },
165     { 0, { 0, 0, 0 } },
166     { 0, { 0, 0, 0 } },
167     { 0, { 0, 0, 0 } },
168     { 1, { 3, 0, 0 } },
169     { 2, { 1, 3, 0 } },
170     { 1, { 1, 0, 0 } }
171 }, upright[8]={
172     { 2, { 3, 5, 0 } },
173     { 1, { 3, 0, 0 } },
174     { 0, { 0, 0, 0 } },
175     { 0, { 0, 0, 0 } },
176     { 0, { 0, 0, 0 } },
177     { 0, { 0, 0, 0 } },
178     { 0, { 0, 0, 0 } },
179     { 1, { 5, 0, 0 } }
180 }, lowleft[8]={
181     { 3, { 7, 0, 1 } },
182     { 0, { 0, 0, 0 } },
183     { 0, { 0, 0, 0 } },
184     { 1, { 1, 0, 0 } },
185     { 2, { 1, 7, 0 } },
186     { 1, { 7, 0, 0 } },
187     { 0, { 0, 0, 0 } },
188     { 0, { 0, 0, 0 } }
189 }, lowright[8]={
190     { 0, { 0, 0, 0 } },
191     { 1, { 7, 0, 0 } },
192     { 2, { 5, 7, 0 } },
193     { 1, { 5, 0, 0 } },
194     { 0, { 0, 0, 0 } },
195     { 0, { 0, 0, 0 } },
196     { 0, { 0, 0, 0 } },
197     { 0, { 0, 0, 0 } }
198 };
199 /* *INDENT-ON* */
200
201 #if HAVE_USE_WINDOW
202 static int
203 safe_wgetch(WINDOW *w, void *data GCC_UNUSED)
204 {
205     return wgetch(w);
206 }
207 static int
208 safe_wrefresh(WINDOW *w, void *data GCC_UNUSED)
209 {
210     return wrefresh(w);
211 }
212 #endif
213
214 #ifdef KEY_RESIZE
215 static void
216 failed(const char *s)
217 {
218     perror(s);
219     stop_curses();
220     ExitProgram(EXIT_FAILURE);
221 }
222 #endif
223
224 static void
225 cleanup(void)
226 {
227     USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
228     stop_curses();
229 }
230
231 static void
232 onsig(int sig GCC_UNUSED)
233 {
234     cleanup();
235     ExitProgram(EXIT_FAILURE);
236 }
237
238 static double
239 ranf(void)
240 {
241     long r = (rand() & 077777);
242     return ((double) r / 32768.);
243 }
244
245 static int
246 draw_worm(WINDOW *win, void *data)
247 {
248     WORM *w = (WORM *) data;
249     const struct options *op;
250     unsigned mask = (unsigned) (~(1 << (w - worm)));
251     chtype attrs = w->attrs | ((mask & pending) ? A_REVERSE : 0);
252
253     int x;
254     int y;
255     int h;
256
257     bool done = FALSE;
258
259     if ((x = w->xpos[h = w->head]) < 0) {
260         wmove(win, y = w->ypos[h] = last_y, x = w->xpos[h] = 0);
261         waddch(win, attrs);
262         refs[y][x]++;
263     } else {
264         y = w->ypos[h];
265     }
266
267     if (x > last_x)
268         x = last_x;
269     if (y > last_y)
270         y = last_y;
271
272     if (++h == length)
273         h = 0;
274
275     if (w->xpos[w->head = h] >= 0) {
276         int x1, y1;
277         x1 = w->xpos[h];
278         y1 = w->ypos[h];
279         if (y1 < LINES
280             && x1 < COLS
281             && --refs[y1][x1] == 0) {
282             wmove(win, y1, x1);
283             waddch(win, trail);
284         }
285     }
286
287     op = &(x == 0
288            ? (y == 0
289               ? upleft
290               : (y == last_y
291                  ? lowleft
292                  : left))
293            : (x == last_x
294               ? (y == 0
295                  ? upright
296                  : (y == last_y
297                     ? lowright
298                     : right))
299               : (y == 0
300                  ? upper
301                  : (y == last_y
302                     ? lower
303                     : normal))))[w->orientation];
304
305     switch (op->nopts) {
306     case 0:
307         done = TRUE;
308         Trace(("done - draw_worm"));
309         break;
310     case 1:
311         w->orientation = op->opts[0];
312         break;
313     default:
314         w->orientation = op->opts[(int) (ranf() * (double) op->nopts)];
315         break;
316     }
317
318     if (!done) {
319         x += xinc[w->orientation];
320         y += yinc[w->orientation];
321         wmove(win, y, x);
322
323         if (y < 0)
324             y = 0;
325         waddch(win, attrs);
326
327         w->ypos[h] = y;
328         w->xpos[h] = x;
329         refs[y][x]++;
330     }
331
332     return done;
333 }
334
335 #ifdef USE_PTHREADS
336 static bool
337 quit_worm(int bitnum)
338 {
339     pending = (pending | (unsigned) (1 << bitnum));
340     napms(10);                  /* let the other thread(s) have a chance */
341     pending = (pending & (unsigned) ~(1 << bitnum));
342     return quitting;
343 }
344
345 static void *
346 start_worm(void *arg)
347 {
348     unsigned long compare = 0;
349     Trace(("start_worm"));
350     while (!quit_worm((int) (((struct worm *) arg) - worm))) {
351         while (compare < sequence) {
352             ++compare;
353             USING_WINDOW2(stdscr, draw_worm, arg);
354         }
355     }
356     Trace(("...start_worm (done)"));
357     return NULL;
358 }
359 #endif
360
361 static bool
362 draw_all_worms(void)
363 {
364     bool done = FALSE;
365     int n;
366     struct worm *w;
367
368 #ifdef USE_PTHREADS
369     static bool first = TRUE;
370     if (first) {
371         first = FALSE;
372         for (n = 0, w = &worm[0]; n < number; n++, w++) {
373             (void) pthread_create(&(w->thread), NULL, start_worm, w);
374         }
375     }
376 #else
377     for (n = 0, w = &worm[0]; n < number; n++, w++) {
378         if (USING_WINDOW2(stdscr, draw_worm, w))
379             done = TRUE;
380     }
381 #endif
382     return done;
383 }
384
385 static int
386 get_input(void)
387 {
388     int ch;
389     ch = USING_WINDOW1(stdscr, wgetch, safe_wgetch);
390     return ch;
391 }
392
393 #ifdef KEY_RESIZE
394 static int
395 update_refs(WINDOW *win, void *data)
396 {
397     int x, y;
398
399     (void) win;
400     (void) data;
401     if (last_x != COLS - 1) {
402         for (y = 0; y <= last_y; y++) {
403             refs[y] = typeRealloc(int, (size_t) COLS, refs[y]);
404             if (!refs[y])
405                 failed("update_refs");
406             for (x = last_x + 1; x < COLS; x++)
407                 refs[y][x] = 0;
408         }
409         last_x = COLS - 1;
410     }
411     if (last_y != LINES - 1) {
412         for (y = LINES; y <= last_y; y++)
413             free(refs[y]);
414         max_refs = LINES;
415         refs = typeRealloc(int *, (size_t) LINES, refs);
416         for (y = last_y + 1; y < LINES; y++) {
417             refs[y] = typeMalloc(int, (size_t) COLS);
418             if (!refs[y])
419                 failed("update_refs");
420             for (x = 0; x < COLS; x++)
421                 refs[y][x] = 0;
422         }
423         last_y = LINES - 1;
424     }
425     return OK;
426 }
427 #endif
428
429 static void
430 usage(void)
431 {
432     static const char *msg[] =
433     {
434         "Usage: worm [options]"
435         ,""
436         ,"Options:"
437 #if HAVE_USE_DEFAULT_COLORS
438         ," -d       invoke use_default_colors"
439 #endif
440         ," -f       fill screen with copies of \"WORM\" at start"
441         ," -l <n>   set length of worms"
442         ," -n <n>   set number of worms"
443         ," -t       leave trail of \".\""
444 #ifdef TRACE
445         ," -T <start>,<end> set trace interval"
446         ," -N       suppress cursor-movement optimization"
447 #endif
448     };
449     size_t n;
450
451     for (n = 0; n < SIZEOF(msg); n++)
452         fprintf(stderr, "%s\n", msg[n]);
453
454     ExitProgram(EXIT_FAILURE);
455 }
456
457 int
458 main(int argc, char *argv[])
459 {
460     int ch;
461     int x, y;
462     int n;
463     struct worm *w;
464     int *ip;
465     bool done = FALSE;
466 #if HAVE_USE_DEFAULT_COLORS
467     bool opt_d = FALSE;
468 #endif
469
470     setlocale(LC_ALL, "");
471
472     while ((ch = getopt(argc, argv, "dfl:n:tT:N")) != -1) {
473         switch (ch) {
474 #if HAVE_USE_DEFAULT_COLORS
475         case 'd':
476             opt_d = TRUE;
477             break;
478 #endif
479         case 'f':
480             field = "WORM";
481             break;
482         case 'l':
483             if ((length = atoi(optarg)) < 2 || length > MAX_LENGTH) {
484                 fprintf(stderr, "%s: Invalid length\n", *argv);
485                 usage();
486             }
487             break;
488         case 'n':
489             if ((number = atoi(optarg)) < 1 || number > MAX_WORMS) {
490                 fprintf(stderr, "%s: Invalid number of worms\n", *argv);
491                 usage();
492             }
493             break;
494         case 't':
495             trail = '.';
496             break;
497 #ifdef TRACE
498         case 'T':
499             if (sscanf(optarg, "%d,%d", &trace_start, &trace_end) != 2)
500                 usage();
501             break;
502         case 'N':
503             _nc_optimize_enable ^= OPTIMIZE_ALL;        /* declared by ncurses */
504             break;
505 #endif /* TRACE */
506         default:
507             usage();
508             /* NOTREACHED */
509         }
510     }
511     if (optind < argc)
512         usage();
513
514     signal(SIGINT, onsig);
515     initscr();
516     noecho();
517     cbreak();
518     nonl();
519
520     curs_set(0);
521
522     last_y = LINES - 1;
523     last_x = COLS - 1;
524
525 #ifdef A_COLOR
526     if (has_colors()) {
527         int bg = COLOR_BLACK;
528         start_color();
529 #if HAVE_USE_DEFAULT_COLORS
530         if (opt_d && (use_default_colors() == OK))
531             bg = -1;
532 #endif
533
534 #define SET_COLOR(num, fg) \
535             init_pair(num+1, (short) fg, (short) bg); \
536             flavor[num] |= (chtype) COLOR_PAIR(num+1) | A_BOLD
537
538         SET_COLOR(0, COLOR_GREEN);
539         SET_COLOR(1, COLOR_RED);
540         SET_COLOR(2, COLOR_CYAN);
541         SET_COLOR(3, COLOR_WHITE);
542         SET_COLOR(4, COLOR_MAGENTA);
543         SET_COLOR(5, COLOR_BLUE);
544         SET_COLOR(6, COLOR_YELLOW);
545     }
546 #endif /* A_COLOR */
547
548     max_refs = LINES;
549     refs = typeMalloc(int *, (size_t) max_refs);
550     for (y = 0; y < max_refs; y++) {
551         refs[y] = typeMalloc(int, (size_t) COLS);
552         for (x = 0; x < COLS; x++) {
553             refs[y][x] = 0;
554         }
555     }
556
557 #ifdef BADCORNER
558     /* if addressing the lower right corner doesn't work in your curses */
559     refs[last_y][last_x] = 1;
560 #endif /* BADCORNER */
561
562     for (n = number, w = &worm[0]; --n >= 0; w++) {
563         w->attrs = flavor[(unsigned) n % SIZEOF(flavor)];
564         w->orientation = 0;
565         w->head = 0;
566
567         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
568             fprintf(stderr, "%s: out of memory\n", *argv);
569             ExitProgram(EXIT_FAILURE);
570         }
571         w->xpos = ip;
572         for (x = length; --x >= 0;)
573             *ip++ = -1;
574         if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
575             fprintf(stderr, "%s: out of memory\n", *argv);
576             ExitProgram(EXIT_FAILURE);
577         }
578         w->ypos = ip;
579         for (y = length; --y >= 0;)
580             *ip++ = -1;
581     }
582     if (field) {
583         const char *p;
584         p = field;
585         for (y = last_y; --y >= 0;) {
586             for (x = COLS; --x >= 0;) {
587                 addch((chtype) (*p++));
588                 if (!*p)
589                     p = field;
590             }
591         }
592     }
593     USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
594     nodelay(stdscr, TRUE);
595
596     while (!done) {
597         ++sequence;
598         if ((ch = get_input()) > 0) {
599 #ifdef TRACE
600             if (trace_start || trace_end) {
601                 if (generation == trace_start) {
602                     curses_trace(TRACE_CALLS);
603                     get_input();
604                 } else if (generation == trace_end) {
605                     curses_trace(0);
606                     get_input();
607                 }
608
609                 generation++;
610             }
611 #endif
612
613 #ifdef KEY_RESIZE
614             if (ch == KEY_RESIZE) {
615                 USING_WINDOW(stdscr, update_refs);
616             }
617 #endif
618
619             /*
620              * Make it simple to put this into single-step mode, or resume
621              * normal operation -T.Dickey
622              */
623             if (ch == 'q') {
624                 quitting = TRUE;
625                 done = TRUE;
626                 Trace(("done - quitting"));
627                 continue;
628             } else if (ch == 's') {
629                 nodelay(stdscr, FALSE);
630             } else if (ch == ' ') {
631                 nodelay(stdscr, TRUE);
632             }
633         }
634
635         done = draw_all_worms();
636         napms(10);
637         USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
638     }
639
640     Trace(("Cleanup"));
641     cleanup();
642 #if NO_LEAKS
643     for (y = 0; y < max_refs; y++) {
644         free(refs[y]);
645     }
646     free(refs);
647     for (n = number, w = &worm[0]; --n >= 0; w++) {
648         free(w->xpos);
649         free(w->ypos);
650     }
651 #endif
652 #ifdef USE_PTHREADS
653     /*
654      * Do this just in case one of the threads did not really exit.
655      */
656     Trace(("join all threads"));
657     for (n = 0; n < number; n++) {
658         pthread_join(worm[n].thread, NULL);
659     }
660 #endif
661     ExitProgram(EXIT_SUCCESS);
662 }