1 /****************************************************************************
2 * Copyright 2018-2019,2020 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.82 2020/02/02 23:34:34 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
safe_wgetch(WINDOW * w,void * data GCC_UNUSED)203 safe_wgetch(WINDOW *w, void *data GCC_UNUSED)
204 {
205 return wgetch(w);
206 }
207 static int
safe_wrefresh(WINDOW * w,void * data GCC_UNUSED)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
failed(const char * s)216 failed(const char *s)
217 {
218 perror(s);
219 stop_curses();
220 ExitProgram(EXIT_FAILURE);
221 }
222 #endif
223
224 static void
cleanup(void)225 cleanup(void)
226 {
227 USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
228 stop_curses();
229 }
230
231 static void
onsig(int sig GCC_UNUSED)232 onsig(int sig GCC_UNUSED)
233 {
234 cleanup();
235 ExitProgram(EXIT_FAILURE);
236 }
237
238 static double
ranf(void)239 ranf(void)
240 {
241 long r = (rand() & 077777);
242 return ((double) r / 32768.);
243 }
244
245 static int
draw_worm(WINDOW * win,void * data)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
quit_worm(int bitnum)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 *
start_worm(void * arg)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 #if HAVE_USE_WINDOW
354 use_window(stdscr, draw_worm, arg);
355 #else
356 draw_worm(stdscr, arg);
357 #endif
358 }
359 }
360 Trace(("...start_worm (done)"));
361 return NULL;
362 }
363 #endif
364
365 static bool
draw_all_worms(void)366 draw_all_worms(void)
367 {
368 bool done = FALSE;
369 int n;
370 struct worm *w;
371
372 #ifdef USE_PTHREADS
373 static bool first = TRUE;
374 if (first) {
375 first = FALSE;
376 for (n = 0, w = &worm[0]; n < number; n++, w++) {
377 (void) pthread_create(&(w->thread), NULL, start_worm, w);
378 }
379 }
380 #else
381 for (n = 0, w = &worm[0]; n < number; n++, w++) {
382 if (
383 #if HAVE_USE_WINDOW
384 USING_WINDOW2(stdscr, draw_worm, w)
385 #else
386 draw_worm(stdscr, w)
387 #endif
388 )
389 done = TRUE;
390 }
391 #endif
392 return done;
393 }
394
395 static int
get_input(void)396 get_input(void)
397 {
398 int ch;
399 ch = USING_WINDOW1(stdscr, wgetch, safe_wgetch);
400 return ch;
401 }
402
403 #ifdef KEY_RESIZE
404 static int
update_refs(WINDOW * win,void * data)405 update_refs(WINDOW *win, void *data)
406 {
407 int x, y;
408
409 (void) win;
410 (void) data;
411 if (last_x != COLS - 1) {
412 for (y = 0; y <= last_y; y++) {
413 refs[y] = typeRealloc(int, (size_t) COLS, refs[y]);
414 if (!refs[y])
415 failed("update_refs");
416 for (x = last_x + 1; x < COLS; x++)
417 refs[y][x] = 0;
418 }
419 last_x = COLS - 1;
420 }
421 if (last_y != LINES - 1) {
422 for (y = LINES; y <= last_y; y++)
423 free(refs[y]);
424 max_refs = LINES;
425 refs = typeRealloc(int *, (size_t) LINES, refs);
426 for (y = last_y + 1; y < LINES; y++) {
427 refs[y] = typeMalloc(int, (size_t) COLS);
428 if (!refs[y])
429 failed("update_refs");
430 for (x = 0; x < COLS; x++)
431 refs[y][x] = 0;
432 }
433 last_y = LINES - 1;
434 }
435 return OK;
436 }
437 #endif
438
439 static void
usage(void)440 usage(void)
441 {
442 static const char *msg[] =
443 {
444 "Usage: worm [options]"
445 ,""
446 ,"Options:"
447 #if HAVE_USE_DEFAULT_COLORS
448 ," -d invoke use_default_colors"
449 #endif
450 ," -f fill screen with copies of \"WORM\" at start"
451 ," -l <n> set length of worms"
452 ," -n <n> set number of worms"
453 ," -t leave trail of \".\""
454 #ifdef TRACE
455 ," -T <start>,<end> set trace interval"
456 ," -N suppress cursor-movement optimization"
457 #endif
458 };
459 size_t n;
460
461 for (n = 0; n < SIZEOF(msg); n++)
462 fprintf(stderr, "%s\n", msg[n]);
463
464 ExitProgram(EXIT_FAILURE);
465 }
466
467 int
main(int argc,char * argv[])468 main(int argc, char *argv[])
469 {
470 int ch;
471 int x, y;
472 int n;
473 struct worm *w;
474 int *ip;
475 bool done = FALSE;
476 #if HAVE_USE_DEFAULT_COLORS
477 bool opt_d = FALSE;
478 #endif
479
480 setlocale(LC_ALL, "");
481
482 while ((ch = getopt(argc, argv, "dfl:n:tT:N")) != -1) {
483 switch (ch) {
484 #if HAVE_USE_DEFAULT_COLORS
485 case 'd':
486 opt_d = TRUE;
487 break;
488 #endif
489 case 'f':
490 field = "WORM";
491 break;
492 case 'l':
493 if ((length = atoi(optarg)) < 2 || length > MAX_LENGTH) {
494 fprintf(stderr, "%s: Invalid length\n", *argv);
495 usage();
496 }
497 break;
498 case 'n':
499 if ((number = atoi(optarg)) < 1 || number > MAX_WORMS) {
500 fprintf(stderr, "%s: Invalid number of worms\n", *argv);
501 usage();
502 }
503 break;
504 case 't':
505 trail = '.';
506 break;
507 #ifdef TRACE
508 case 'T':
509 if (sscanf(optarg, "%d,%d", &trace_start, &trace_end) != 2)
510 usage();
511 break;
512 case 'N':
513 _nc_optimize_enable ^= OPTIMIZE_ALL; /* declared by ncurses */
514 break;
515 #endif /* TRACE */
516 default:
517 usage();
518 /* NOTREACHED */
519 }
520 }
521 if (optind < argc)
522 usage();
523
524 signal(SIGINT, onsig);
525 initscr();
526 noecho();
527 cbreak();
528 nonl();
529
530 curs_set(0);
531
532 last_y = LINES - 1;
533 last_x = COLS - 1;
534
535 #ifdef A_COLOR
536 if (has_colors()) {
537 int bg = COLOR_BLACK;
538 start_color();
539 #if HAVE_USE_DEFAULT_COLORS
540 if (opt_d && (use_default_colors() == OK))
541 bg = -1;
542 #endif
543
544 #define SET_COLOR(num, fg) \
545 init_pair(num+1, (short) fg, (short) bg); \
546 flavor[num] |= (chtype) COLOR_PAIR(num+1) | A_BOLD
547
548 SET_COLOR(0, COLOR_GREEN);
549 SET_COLOR(1, COLOR_RED);
550 SET_COLOR(2, COLOR_CYAN);
551 SET_COLOR(3, COLOR_WHITE);
552 SET_COLOR(4, COLOR_MAGENTA);
553 SET_COLOR(5, COLOR_BLUE);
554 SET_COLOR(6, COLOR_YELLOW);
555 }
556 #endif /* A_COLOR */
557
558 max_refs = LINES;
559 refs = typeMalloc(int *, (size_t) max_refs);
560 for (y = 0; y < max_refs; y++) {
561 refs[y] = typeMalloc(int, (size_t) COLS);
562 for (x = 0; x < COLS; x++) {
563 refs[y][x] = 0;
564 }
565 }
566
567 #ifdef BADCORNER
568 /* if addressing the lower right corner doesn't work in your curses */
569 refs[last_y][last_x] = 1;
570 #endif /* BADCORNER */
571
572 for (n = number, w = &worm[0]; --n >= 0; w++) {
573 w->attrs = flavor[(unsigned) n % SIZEOF(flavor)];
574 w->orientation = 0;
575 w->head = 0;
576
577 if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
578 fprintf(stderr, "%s: out of memory\n", *argv);
579 ExitProgram(EXIT_FAILURE);
580 }
581 w->xpos = ip;
582 for (x = length; --x >= 0;)
583 *ip++ = -1;
584 if (!(ip = typeMalloc(int, (size_t) (length + 1)))) {
585 fprintf(stderr, "%s: out of memory\n", *argv);
586 ExitProgram(EXIT_FAILURE);
587 }
588 w->ypos = ip;
589 for (y = length; --y >= 0;)
590 *ip++ = -1;
591 }
592 if (field) {
593 const char *p;
594 p = field;
595 for (y = last_y; --y >= 0;) {
596 for (x = COLS; --x >= 0;) {
597 addch((chtype) (*p++));
598 if (!*p)
599 p = field;
600 }
601 }
602 }
603 USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
604 nodelay(stdscr, TRUE);
605
606 while (!done) {
607 ++sequence;
608 if ((ch = get_input()) > 0) {
609 #ifdef TRACE
610 if (trace_start || trace_end) {
611 if (generation == trace_start) {
612 curses_trace(TRACE_CALLS);
613 get_input();
614 } else if (generation == trace_end) {
615 curses_trace(0);
616 get_input();
617 }
618
619 generation++;
620 }
621 #endif
622
623 #ifdef KEY_RESIZE
624 if (ch == KEY_RESIZE) {
625 USING_WINDOW(stdscr, update_refs);
626 }
627 #endif
628
629 /*
630 * Make it simple to put this into single-step mode, or resume
631 * normal operation -T.Dickey
632 */
633 if (ch == 'q') {
634 quitting = TRUE;
635 done = TRUE;
636 Trace(("done - quitting"));
637 continue;
638 } else if (ch == 's') {
639 nodelay(stdscr, FALSE);
640 } else if (ch == ' ') {
641 nodelay(stdscr, TRUE);
642 }
643 }
644
645 done = draw_all_worms();
646 napms(10);
647 USING_WINDOW1(stdscr, wrefresh, safe_wrefresh);
648 }
649
650 Trace(("Cleanup"));
651 cleanup();
652 #if NO_LEAKS
653 for (y = 0; y < max_refs; y++) {
654 free(refs[y]);
655 }
656 free(refs);
657 for (n = number, w = &worm[0]; --n >= 0; w++) {
658 free(w->xpos);
659 free(w->ypos);
660 }
661 #endif
662 #ifdef USE_PTHREADS
663 /*
664 * Do this just in case one of the threads did not really exit.
665 */
666 Trace(("join all threads"));
667 for (n = 0; n < number; n++) {
668 pthread_join(worm[n].thread, NULL);
669 }
670 #endif
671 ExitProgram(EXIT_SUCCESS);
672 }
673