ncurses 6.2 - patch 20210227
[ncurses.git] / test / rain.c
1 /****************************************************************************
2  * Copyright 2018-2019,2020 Thomas E. Dickey                                *
3  * Copyright 1998-2014,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  * $Id: rain.c,v 1.52 2020/08/29 16:22:03 juergen Exp $
31  */
32 #include <test.priv.h>
33 #include <popup_msg.h>
34
35 /* rain 11/3/1980 EPS/CITHEP */
36
37 #ifdef USE_PTHREADS
38 #include <pthread.h>
39 #endif
40
41 WANT_USE_WINDOW();
42
43 #define MAX_THREADS     10
44 #define MAX_DROP        5
45
46 struct DATA;
47
48 typedef void (*DrawPart) (struct DATA *);
49
50 typedef struct DATA {
51     int y, x;
52 #ifdef USE_PTHREADS
53     DrawPart func;
54     int state;
55 #endif
56 } DATA;
57
58 #ifdef USE_PTHREADS
59 pthread_cond_t cond_next_drop;
60 pthread_mutex_t mutex_next_drop;
61 static int used_threads;
62
63 typedef struct {
64     pthread_t myself;
65     long counter;
66 } STATS;
67
68 static STATS drop_threads[MAX_THREADS];
69 #endif
70
71 #if HAVE_USE_WINDOW
72 static int
73 safe_wgetch(WINDOW *w, void *data GCC_UNUSED)
74 {
75     return wgetch(w);
76 }
77 #endif
78
79 static void
80 onsig(int n GCC_UNUSED)
81 {
82     stop_curses();
83     ExitProgram(EXIT_FAILURE);
84 }
85
86 static double
87 ranf(void)
88 {
89     long r = (rand() & 077777);
90     return ((double) r / 32768.);
91 }
92
93 static int
94 random_x(void)
95 {
96     return (int) (((double) (COLS - 4) * ranf()) + 2);
97 }
98
99 static int
100 random_y(void)
101 {
102     return (int) (((double) (LINES - 4) * ranf()) + 2);
103 }
104
105 static int
106 next_j(int j)
107 {
108     if (j == 0)
109         j = MAX_DROP - 1;
110     else
111         --j;
112     if (has_colors()) {
113         int z = (int) (3 * ranf());
114         (void) attrset(AttrArg(COLOR_PAIR(z), (z ? A_BOLD : A_NORMAL)));
115     }
116     return j;
117 }
118
119 static void
120 part1(DATA * drop)
121 {
122     MvAddCh(drop->y, drop->x, '.');
123 }
124
125 static void
126 part2(DATA * drop)
127 {
128     MvAddCh(drop->y, drop->x, 'o');
129 }
130
131 static void
132 part3(DATA * drop)
133 {
134     MvAddCh(drop->y, drop->x, 'O');
135 }
136
137 static void
138 part4(DATA * drop)
139 {
140     MvAddCh(drop->y - 1, drop->x, '-');
141     MvAddStr(drop->y, drop->x - 1, "|.|");
142     MvAddCh(drop->y + 1, drop->x, '-');
143 }
144
145 static void
146 part5(DATA * drop)
147 {
148     MvAddCh(drop->y - 2, drop->x, '-');
149     MvAddStr(drop->y - 1, drop->x - 1, "/ \\");
150     MvAddStr(drop->y, drop->x - 2, "| O |");
151     MvAddStr(drop->y + 1, drop->x - 1, "\\ /");
152     MvAddCh(drop->y + 2, drop->x, '-');
153 }
154
155 static void
156 part6(DATA * drop)
157 {
158     MvAddCh(drop->y - 2, drop->x, ' ');
159     MvAddStr(drop->y - 1, drop->x - 1, "   ");
160     MvAddStr(drop->y, drop->x - 2, "     ");
161     MvAddStr(drop->y + 1, drop->x - 1, "   ");
162     MvAddCh(drop->y + 2, drop->x, ' ');
163 }
164
165 #ifdef USE_PTHREADS
166 static void
167 napsome(void)
168 {
169     napms(60);
170 }
171
172 /*
173  * This runs inside the use_window() mutex.
174  */
175 static int
176 really_draw(WINDOW *win, void *arg)
177 {
178     DATA *data = (DATA *) arg;
179
180     (void) win;
181     next_j(data->state);
182     data->func(data);
183     refresh();
184     return OK;
185 }
186
187 static void
188 draw_part(void (*func) (DATA *), int state, DATA * data)
189 {
190     data->func = func;
191     data->state = state;
192     use_window(stdscr, really_draw, (void *) data);
193     napsome();
194 }
195
196 /*
197  * Tell the threads that one of them can start work on a new raindrop.
198  * They may all be busy if we're sending requests too rapidly.
199  */
200 static int
201 put_next_drop(void)
202 {
203     pthread_cond_signal(&cond_next_drop);
204     pthread_mutex_unlock(&mutex_next_drop);
205
206     return 0;
207 }
208
209 /*
210  * Wait until we're assigned the task of drawing a new raindrop.
211  */
212 static int
213 get_next_drop(void)
214 {
215     pthread_mutex_lock(&mutex_next_drop);
216     pthread_cond_wait(&cond_next_drop, &mutex_next_drop);
217
218     return TRUE;
219 }
220
221 static void *
222 draw_drop(void *arg)
223 {
224     DATA mydata;
225     int mystats;
226
227     /*
228      * Find myself in the list of threads so we can count the number of loops.
229      */
230     for (mystats = 0; mystats < MAX_THREADS; ++mystats) {
231 #if defined(_NC_WINDOWS) && !defined(__WINPTHREADS_VERSION)
232         if (drop_threads[mystats].myself.p == pthread_self().p)
233 #else
234         if (drop_threads[mystats].myself == pthread_self())
235 #endif
236             break;
237     }
238
239     do {
240         if (mystats < MAX_THREADS)
241             drop_threads[mystats].counter++;
242
243         /*
244          * Make a copy of caller's data.  We're cheating for the cases after
245          * the first loop since we still have a pointer into the main thread
246          * to the data which it uses for setting up this thread (but it has
247          * been modified to use different coordinates).
248          */
249         mydata = *(DATA *) arg;
250
251         draw_part(part1, 0, &mydata);
252         draw_part(part2, 1, &mydata);
253         draw_part(part3, 2, &mydata);
254         draw_part(part4, 3, &mydata);
255         draw_part(part5, 4, &mydata);
256         draw_part(part6, 0, &mydata);
257     } while (get_next_drop());
258
259     return NULL;
260 }
261
262 /*
263  * The description of pthread_create() is misleading, since it implies that
264  * threads will exit cleanly after their function returns.
265  *
266  * Since they do not (and the number of threads is limited by system
267  * resources), make a limited number of threads, and signal any that are
268  * waiting when we want a thread past that limit.
269  */
270 static int
271 start_drop(DATA * data)
272 {
273     int rc;
274
275     if (!used_threads) {
276         /* mutex and condition for signalling thread */
277         pthread_mutex_init(&mutex_next_drop, NULL);
278         pthread_cond_init(&cond_next_drop, NULL);
279     }
280
281     if (used_threads < MAX_THREADS) {
282         rc = pthread_create(&(drop_threads[used_threads].myself),
283                             NULL,
284                             draw_drop,
285                             data);
286         ++used_threads;
287     } else {
288         rc = put_next_drop();
289     }
290     return rc;
291 }
292 #endif
293
294 static int
295 get_input(void)
296 {
297     return USING_WINDOW1(stdscr, wgetch, safe_wgetch);
298 }
299
300 static void
301 usage(void)
302 {
303     static const char *msg[] =
304     {
305         "Usage: rain [options]"
306         ,""
307         ,"Options:"
308 #if HAVE_USE_DEFAULT_COLORS
309         ," -d       invoke use_default_colors"
310 #endif
311     };
312     size_t n;
313
314     for (n = 0; n < SIZEOF(msg); n++)
315         fprintf(stderr, "%s\n", msg[n]);
316
317     ExitProgram(EXIT_FAILURE);
318 }
319
320 int
321 main(int argc, char *argv[])
322 {
323     static const char *help[] =
324     {
325         "Commands:",
326         " q/Q        exit the program",
327         " s          do single-step",
328         " <space>    undo single-step",
329         "",
330         0
331     };
332
333     bool done = FALSE;
334     DATA drop;
335 #ifndef USE_PTHREADS
336     DATA last[MAX_DROP];
337 #endif
338     int j = 0;
339     int ch;
340 #if HAVE_USE_DEFAULT_COLORS
341     bool d_option = FALSE;
342 #endif
343
344     while ((ch = getopt(argc, argv, "d")) != -1) {
345         switch (ch) {
346 #if HAVE_USE_DEFAULT_COLORS
347         case 'd':
348             d_option = TRUE;
349             break;
350 #endif
351         default:
352             usage();
353             /* NOTREACHED */
354         }
355     }
356     if (optind < argc)
357         usage();
358
359     setlocale(LC_ALL, "");
360
361     InitAndCatch(initscr(), onsig);
362     if (has_colors()) {
363         int bg = COLOR_BLACK;
364         start_color();
365 #if HAVE_USE_DEFAULT_COLORS
366         if (d_option && (use_default_colors() == OK))
367             bg = -1;
368 #endif
369         init_pair(1, COLOR_BLUE, (short) bg);
370         init_pair(2, COLOR_CYAN, (short) bg);
371     }
372     nl();
373     noecho();
374     curs_set(0);
375     timeout(0);
376
377 #ifndef USE_PTHREADS
378     for (j = MAX_DROP; --j >= 0;) {
379         last[j].x = random_x();
380         last[j].y = random_y();
381     }
382     j = 0;
383 #endif
384
385     while (!done) {
386         drop.x = random_x();
387         drop.y = random_y();
388
389 #ifdef USE_PTHREADS
390         if (start_drop(&drop) != 0) {
391             beep();
392         }
393 #else
394         /*
395          * The non-threaded code draws parts of each drop on each loop.
396          */
397         part1(&drop);
398
399         part2(&last[j]);
400
401         j = next_j(j);
402         part3(&last[j]);
403
404         j = next_j(j);
405         part4(&last[j]);
406
407         j = next_j(j);
408         part5(&last[j]);
409
410         j = next_j(j);
411         part6(&last[j]);
412
413         last[j] = drop;
414 #endif
415
416         switch (get_input()) {
417         case ('q'):
418         case ('Q'):
419             done = TRUE;
420             break;
421         case 's':
422             nodelay(stdscr, FALSE);
423             break;
424         case ' ':
425             nodelay(stdscr, TRUE);
426             break;
427 #ifdef KEY_RESIZE
428         case (KEY_RESIZE):
429             break;
430 #endif
431         case HELP_KEY_1:
432             popup_msg(stdscr, help);
433             break;
434         case ERR:
435             break;
436         default:
437             beep();
438         }
439         napms(50);
440     }
441     stop_curses();
442 #ifdef USE_PTHREADS
443     printf("Counts per thread:\n");
444     for (j = 0; j < MAX_THREADS; ++j)
445         printf("  %d:%ld\n", j, drop_threads[j].counter);
446 #endif
447     ExitProgram(EXIT_SUCCESS);
448 }