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