ftp://ftp.linux.ncsu.edu/mirror/ftp.redhat.com/pub/redhat/linux/enterprise/4/en/os...
[xscreensaver] / hacks / juggle.c
1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* juggle */
3
4 #if 0
5 static const char sccsid[] = "@(#)juggle.c      5.00 2000/11/01 xlockmore";
6 #endif
7
8 /*-
9  * Copyright (c) 1996 by Tim Auckland <Tim.Auckland@Procket.com>
10  *
11  * Permission to use, copy, modify, and distribute this software and its
12  * documentation for any purpose and without fee is hereby granted,
13  * provided that the above copyright notice appear in all copies and that
14  * both that copyright notice and this permission notice appear in
15  * supporting documentation.
16  *
17  * This file is provided AS IS with no warranties of any kind.  The author
18  * shall have no liability with respect to the infringement of copyrights,
19  * trade secrets or any patents by this file or any part thereof.  In no
20  * event will the author be liable for any lost revenue or profits or
21  * other special, indirect and consequential damages.
22  *
23  * Revision History
24  * 01-Nov-2000: Allocation checks
25  * 1996: Written
26  */
27
28 /*-
29  * TODO
30  * Fix timing to run at approx same speed on all machines.
31  * Store shorter pattern and refill when required.
32  * Use -cycles and -count in a rational manner.
33  * Merge pattern selector with pattern generator.
34  * Add clubs
35  * Clap when all the balls are in the air
36  */
37
38
39 /*-
40 Notes on Adam Chalcraft Juggling Notation (used by permission)
41 a-> Adam's notation  s-> Site swap (Cambridge) notation
42
43 To define a map from a-notation to s-notation
44 ("site-swap"), both of which look like doubly infinite sequences of natural
45 numbers. In s-notation, there is a restriction on what is allowed, namely
46 for the sequence s_n, the associated function f(n)=n+s_n must be a
47 bijection. In a-notation, there is no restriction.
48
49 To go from a-notation to s-notation, you start by mapping each a_n to a
50 permutation of N, the natural numbers.
51
52 0 -> the identity
53 1 -> (10) [i.e. f(1)=0, f(0)=1]
54 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
55 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
56 etc.
57
58 Then for each n, you look at how long 0 takes to get back to 0 again and
59 you call this t_n. If a_n=0, for example, then since the identity leaves 0
60 alone, it gets back to 0 in 1 step, so t_n=1. If a_n=1, then f(0)=1. Now any
61 further a_n=0 leave 1 alone, but the next a_n>0 sends 1 back to 0. Hence t_n
62 is 2 + the number of 0's following the 1. Finally, set s_n = t_n - 1.
63
64 To give some examples, it helps to have a notation for cyclic sequences. By
65 (123), for example, I mean ...123123123123... . Now under the a-notation ->
66 s-notation mapping we have some familiar examples:
67
68 (0)->(0), (1)->(1), (2)->(2) etc.
69 (21)->(31), (31)->(51), (41)->(71) etc.
70 (10)->(20), (20)->(40), (30)->(60) etc.
71 (331)->(441), (312)->(612), (303)->(504), (321)->(531)
72 (43)->(53), (434)->(534), (433)->(633)
73 (552)->(672)
74
75 In general, the number of balls is the *average* of the s-notation, and the
76 *maximum* of the a-notation. Another theorem is that the minimum values in
77 the a-notation and the s-notation and equal, and preserved in the same
78 positions.
79
80 The usefulness of a-notation is the fact that there are no restrictions on
81 what is allowed. This makes random juggle generation much easier. It also
82 makes enumeration very easy. Another handy feature is computing changes.
83 Suppose you can do (5) and want a neat change up to (771) in s-notation
84 [Mike Day actually needed this example!]. Write them both in a-notation,
85 which gives (5) and (551). Now concatenate them (in general, there may be
86 more than one way to do this, but not in this example), to get
87 ...55555555551551551551551...
88 Now convert back to s-notation, to get
89 ...55555566771771771771771...
90 So the answer is to do two 6 throws and then go straight into (771).
91 Coming back down of course,
92 ...5515515515515515555555555...
93 converts to
94 ...7717717717716615555555555...
95 so the answer is to do a single 661 and then drop straight down to (5).
96
97 [The number of balls in the generated pattern occasionally changes.  In
98  order to decrease the number of balls I had to introduce a new symbol
99  into the Adam notation, [*] which means 'lose the current ball'.]
100 */
101
102 #ifdef STANDALONE
103 #define MODE_juggle
104 #define PROGCLASS "Juggle"
105 #define HACK_INIT init_juggle
106 #define HACK_DRAW draw_juggle
107 #define juggle_opts xlockmore_opts
108 #define DEFAULTS "*delay: 10000 \n" \
109 "*count: 150 \n" \
110 "*cycles: 30 \n" \
111 "*ncolors: 32 \n"
112 #define SMOOTH_COLORS
113 #include "xlockmore.h"          /* in xscreensaver distribution */
114 #else /* STANDALONE */
115 #include "xlock.h"              /* in xlockmore distribution */
116 #endif /* STANDALONE */
117
118 #ifdef MODE_juggle
119
120 #define DEF_PATTERN "." /* All patterns */
121 #define DEF_TRAIL "0" /* No trace */
122 #ifdef UNI
123 #define DEF_UNI "FALSE" /* No unicycle */ /* Not implemented yet */
124 #endif
125 #define DEF_SOLID "FALSE" /* Not solid */
126
127 static char *pattern;
128 static int trail;
129 #ifdef UNI
130 static Bool uni;
131 #endif
132 static Bool solid;
133
134 static XrmOptionDescRec opts[] =
135 {
136   {(char* ) "-pattern", ".juggle.pattern", XrmoptionSepArg, NULL},
137   {(char* ) "-trail", ".juggle.trail",     XrmoptionSepArg, NULL},
138 #ifdef UNI
139   {"-uni", ".juggle.uni", XrmoptionNoArg, "on"},
140   {"+uni", ".juggle.uni", XrmoptionNoArg, "off"},
141 #endif
142   {"-solid", ".juggle.solid", XrmoptionNoArg, "on"},
143   {"+solid", ".juggle.solid", XrmoptionNoArg, "off"}
144 };
145 static argtype vars[] =
146 {
147   {&pattern, "pattern", 
148    "Pattern", (char *) DEF_PATTERN, t_String},
149   {&trail, "trail", "Trail", DEF_TRAIL, t_Int},
150 #ifdef UNI
151   {&uni, "uni", "Uni", DEF_UNI, t_Bool},
152 #endif
153   {&solid, "solid", "Solid", DEF_SOLID, t_Bool}
154 };
155 static OptionStruct desc[] =
156 {
157   {"-pattern string", "Cambridge Juggling Pattern"},
158   {"-trail num", "Trace Juggling Patterns"},
159 #ifdef UNI
160   {"-/+uni", "Unicycle"},
161 #endif
162   {"-/+solid", "solid color (else its a 4 panel look (half white))"}
163 };
164
165 ModeSpecOpt juggle_opts =
166 {sizeof opts / sizeof opts[0], opts,
167  sizeof vars / sizeof vars[0], vars, desc};
168
169 #ifdef USE_MODULES
170 ModStruct   juggle_description = {
171         "juggle", "init_juggle", "draw_juggle", "release_juggle",
172         "draw_juggle", "init_juggle", (char *) NULL, &juggle_opts,
173         10000, 150, 30, 1, 64, 1.0, "",
174         "Shows a Juggler, juggling", 0, NULL
175 };
176
177 #endif
178
179 #ifdef USE_XVMSUTILS
180 #include <X11/unix_time.h>
181 #endif
182 #include <time.h>
183 #if HAVE_SYS_TIME_H
184 #include <sys/time.h>
185 #else
186 #if HAVE_SYS_SELECT_H
187 #include <sys/select.h>
188 #endif
189 #endif
190
191 /* Figure */
192 #define ARMLENGTH ((int) (40.0 * sp->scale))
193 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
194 #define POSE ((int) (10.0 * sp->scale))
195 #define SX ((int) (25.0 * sp->scale))
196 #define SZ ((int) (25.0 * sp->scale))
197 #define SY ((int) (25.0 * sp->scale))
198 #define HIPY ((int) (85.0 * sp->scale))
199 #define RHIPX ((int) (-15.0 * sp->scale))
200 #define LHIPX ((int) (15.0 * sp->scale))
201 #define RFX ((int) (-25.0 * sp->scale))
202 #define LFX ((int) (25.0 * sp->scale))
203 #define FY ((int) (155.0 * sp->scale))
204 #define WSTY ((int) (65.0 * sp->scale))
205 #define NEY ((int) (15.0 * sp->scale))
206 #define HED ((int) (35.0 * sp->scale))
207 #define BALLRADIUS ARMWIDTH
208 #define FIGURE1 7
209 #define FIGURE2 3
210 #define TRACE_LENGTH 50
211 #define SPIN_DEGREES 750  /* Average spinning between a throw and the next catch */
212
213 /* macros */
214
215 #ifndef XtNumber
216 #define XtNumber(arr)   ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
217 #endif
218
219 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
220
221 #define THROW_CATCH_INTERVAL (sp->count)
222 #define THROW_NULL_INTERVAL  (sp->count * 0.5)
223 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
224 #define COR 0.8  /* coeff of restitution of balls (1 = perfect bounce) */
225
226
227 /* typedefs */
228
229 typedef enum {HEIGHT, ADAM} Notation;
230 typedef enum {Empty, Full, Ball} Throwable;
231 typedef enum {LEFT, RIGHT} Hand;
232 typedef enum {THROW, CATCH} Action; /* DROP is not an option */
233 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, PTHRATCH, BPREDICTOR,
234         PREDICTOR} TrajectoryStatus;
235
236 typedef struct trajectory *TrajectoryPtr;
237
238 typedef struct {double a, b, c, d; } Spline;
239
240 typedef struct trajectory {
241   TrajectoryPtr prev, next;  /* for building list */
242   TrajectoryStatus status;
243
244   /* Throw */
245   char posn;
246   int height;
247   int adam;
248
249   /* Action */
250   Hand hand;
251   Action action;
252
253   /* LinkedAction */
254   int color;
255   int spin, divisions;
256   double degree_offset;
257   TrajectoryPtr balllink;
258   TrajectoryPtr handlink;
259
260   /* PThratch */
261
262   double dx; /* initial velocity */
263   double dy;
264
265   /* Predictor */
266   Throwable type;
267   int start, finish;
268   Spline xp, yp;
269   int x, y; /* current position */
270 } Trajectory;
271
272 /* structs */
273
274 typedef struct {
275   int         width;
276   int         height;
277   double      scale;
278   int         complexity;
279   int         cx;
280   int         cy;
281   double      Gr;
282   int         pattern;
283   Trajectory  *head;
284   XPoint   figure_path[FIGURE1];
285   XSegment figure_segs[FIGURE2];
286   XPoint      arm[2][3];
287   XPoint      *trace;
288   int         traceindex;
289   int         count;
290   time_t      begintime; /* seconds */
291   int         time; /* millisecond timer */
292   Bool        solid, uni;       
293 } jugglestruct;
294
295 static jugglestruct *juggles = (jugglestruct *) NULL;
296
297 typedef struct {
298   char * pattern;
299   char * name;
300 } patternstruct;
301
302 #define MINBALLS 2
303 #define MAXBALLS 7
304
305 typedef struct {
306   int start;
307   int number;
308 } PatternIndex;
309
310 static PatternIndex* patternindex = (PatternIndex *) NULL;
311
312 /* List of popular patterns, in any order */
313 static patternstruct portfolio[] = {
314   {"[+2 1]", "+3 1, Typical 2 ball juggler"},
315   {"[2 0]", "4 0, 2 balls 1 hand"},
316   {"[2 0 1]", "5 0 1"},
317   {"[+2 0 +2 0 0]", "+5 0 +5 0 0"},
318   {"[3]", "3, cascade"},
319   {"[+3]", "+3, reverse cascade"},
320   {"[=3]", "=3, cascade under arm"},
321   {"[&3]", "&3, cascade catching under arm"},
322   {"[_3]", "_3, bouncing cascade"},
323   {"[+3 x3 =3]", "+3 x3 =3, Mill's mess"},
324   {"[3 2 1]", "5 3 1"},
325   {"[3 3 1]", "4 4 1"},
326   {"[3 1 2]", "6 1 2, See-saw"},
327   {"[=3 3 1 2]", "=4 5 1 2"},
328   {"[=3 2 2 3 1 2]", "=6 2 2 5 1 2, =4 5 1 2 stretched"},
329   {"[+3 3 1 3]", "+4 4 1 3, anemic shower box"},
330   {"[3 3 1]", "4 4 1"},
331   {"[+3 2 3]", "+4 2 3"},
332   {"[+3 1]", "+5 1, 3 shower"},
333   {"[_3 1]", "_5 1, bouncing 3 shower"},
334   {"[3 0 3 0 3]", "5 0 5 0 5, shake 3 out of 5"},
335   {"[3 3 3 0 0]", "5 5 5 0 0, flash 3 out of 5"},
336   {"[3 3 0]", "4 5 0, complete waste of a 5 ball juggler"},
337   {"[3 3 3 0 0 0 0]", "7 7 7 0 0 0 0, 3 flash"},
338   {"[+3 0 +3 0 +3 0 0]", "+7 0 +7 0 +7 0 0"},
339   {"[4]", "4, 4 cascade"},
340   {"[+4 3]", "+5 3, 4 ball half shower"},
341   {"[4 4 2]", "5 5 2"},
342   {"[+4 4 4 +4]", "+4 4 4 +4, 4 columns"},
343   {"[4 3 +4]", "5 3 +4"},
344   {"[+4 1]", "+7 1, 4 shower"},
345   {"[4 4 4 4 0]", "5 5 5 5 0, learning 5"},
346   {"[5]", "5, 5 cascade"},
347   {"[_5 _5 _5 _5 _5 5 5 5 5 5]", "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"},
348   {"[+5 x5 =5]", "+5 x5 =5, Mill's mess for 5"},
349   {"[6]", "6, 6 cascade"},
350   {"[7]", "7, 7 cascade"},
351   {"[_7]", "_7, bouncing 7 cascade"},
352 };
353
354 /* Private Functions */
355
356 /* list management */
357
358 /* t receives trajectory to be created.  ot must point to an existing
359    trajectory or be identical to t to start a new list. */
360 #define INSERT_AFTER_TOP(t, ot)                                 \
361   if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
362     {free_juggle(sp); return;}                                  \
363   (t)->next = (ot)->next;                                       \
364   (t)->prev = (ot);                                             \
365   (ot)->next = (t);                                             \
366   (t)->next->prev = (t)
367 #define INSERT_AFTER(t, ot)                                     \
368   if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
369     {free_juggle(sp); return False;}                            \
370   (t)->next = (ot)->next;                                       \
371   (t)->prev = (ot);                                             \
372   (ot)->next = (t);                                             \
373   (t)->next->prev = (t)
374
375
376 /* t must point to an existing trajectory.  t must not be an
377    expression ending ->next or ->prev */
378 #define REMOVE(t)                                               \
379   (t)->next->prev = (t)->prev;                                  \
380   (t)->prev->next = (t)->next;                                  \
381   (void) free((void *) t)
382
383 static void
384 free_pattern(jugglestruct *sp) {
385         if (sp->head != NULL) {
386                 while (sp->head->next != sp->head) {
387                         Trajectory *t = sp->head->next;
388
389                         REMOVE(t); /* don't eliminate t */
390                 }
391                 (void) free((void *) sp->head);
392                 sp->head = (Trajectory *) NULL;
393         }
394 }
395
396 static void
397 free_juggle(jugglestruct *sp)
398 {
399         if (sp->trace != NULL) {
400                 (void) free((void *) sp->trace);
401                 sp->trace = (XPoint *) NULL;
402         }
403         free_pattern(sp);
404 }
405
406 static Bool
407 add_throw(jugglestruct *sp, char type, int h, Notation n)
408 {
409   Trajectory *t;
410
411   INSERT_AFTER(t, sp->head->prev);
412   t->posn = type;
413   if (n == ADAM) {
414         t->adam = h;
415         t->status = ATCH;
416   } else {
417         t->height = h;
418         t->status = THRATCH;
419   }
420   return True;
421 }
422
423 /* add a Thratch to the performance */
424 static Bool
425 program(ModeInfo *mi, const char *patn, int repeat)
426 {
427   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
428   const char *p;
429   int h, i, seen;
430   Notation notation;
431   char type;
432
433   if (MI_IS_VERBOSE(mi)) {
434         (void) fprintf(stderr, "%s x %d\n", patn, repeat);
435   }
436
437   for(i=0; i < repeat; i++) {
438         type=' ';
439         h = 0;
440         seen = 0;
441         notation = HEIGHT;
442         for(p=patn; *p; p++) {
443                 if (*p >= '0' && *p <='9') {
444                 seen = 1;
445                 h = 10*h + (*p - '0');
446           } else {
447                 Notation nn = notation;
448                 switch (*p) {
449                 case '[':            /* begin Adam notation */
450                   notation = ADAM;
451                   break;
452                 case '-':            /* Inside throw */
453                 case '+':            /* Outside throw */
454                 case '=':            /* Cross throw */
455                 case '&':            /* Cross catch */
456                 case 'x':            /* Cross throw and catch */
457                 case '_':            /* Bounce */
458                   type = *p;
459                   break;
460                 case '*':            /* Lose ball */
461                   seen = 1;
462                   h = -1;
463                   /* fall through */
464                 case ']':             /* end Adam notation */
465                   nn = HEIGHT;
466                   /* fall through */
467                 case ' ':
468                   if (seen) {
469                         if (!add_throw(sp, type, h, notation))
470                                 return False;
471                         type=' ';
472                         h = 0;
473                         seen = 0;
474                   }
475                   notation = nn;
476                   break;
477                 default:
478                   (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p);
479                   break;
480                 }
481           }
482         }
483         if (seen) {
484           if (!add_throw(sp, type, h, notation))
485                 return False;
486         }
487   }
488         return True;
489 }
490
491 /*
492  ~~~~\~~~~~\~~~
493  \\~\\~\~\\\~~~
494  \\~\\\\~\\\~\~
495  \\\\\\\\\\\~\\
496
497 [33134231334021]
498
499 4 4 1 3 12 2 4 1 4 4 13 0 3 1
500
501 */
502 #define BOUNCEOVER 10
503
504 static void
505 adam(jugglestruct *sp)
506 {
507   Trajectory *t, *p;
508   for(t = sp->head->next; t != sp->head; t = t->next) {
509         if (t->status == ATCH) {
510           int a = t->adam;
511           t->height = 0;
512           for(p = t->next; a > 0 && p != sp->head; p = p->next) {
513                 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
514                   a--;
515                 }
516                 t->height++;
517           }
518           if(t->height > BOUNCEOVER && t->posn == ' '){
519                 t->posn = '_'; /* high defaults can be bounced */
520           }
521           t->status = THRATCH;
522 #if 0
523           (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height);
524 #endif
525         }
526   }
527 }
528
529 /* Split Thratch notation into explicit throws and catches.
530    Usually Catch follows Throw in same hand, but take care of special
531    cases. */
532
533 /* ..n1.. -> .. LTn RT1 LC RC .. */
534 /* ..nm.. -> .. LTn LC RTm RC .. */
535
536 static Bool
537 part(jugglestruct *sp)
538 {
539   Trajectory *t, *nt, *p;
540   Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
541
542   for (t = sp->head->next; t != sp->head; t = t->next) {
543         if (t->status > THRATCH) {
544           hand = t->hand;
545         } else if (t->status == THRATCH) {
546           char posn = '=';
547
548           /* plausibility check */
549           if (t->height <= 2 && t->posn == '_') {
550                 t->posn = ' '; /* no short bounces */
551           }
552           if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
553                 t->posn = ' '; /* 1's need close catches */
554           }
555
556           switch (t->posn) {
557                   /*         throw          catch    */
558           case ' ': /* fall through */
559           case '-': posn = '-'; t->posn = '+'; break;
560           case '+': posn = '+'; t->posn = '-'; break;
561           case '=': posn = '='; t->posn = '+'; break;
562           case '&': posn = '+'; t->posn = '='; break;
563           case 'x': posn = '='; t->posn = '='; break;
564           case '_': posn = '_'; t->posn = '-'; break;
565           default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
566           }
567           hand = (Hand) ((hand + 1) % 2);
568           t->status = ACTION;
569           t->hand = hand;
570           p = t->prev;
571
572           if (t->height == 1) {
573                 p = p->prev; /* early throw */
574           }
575           t->action = CATCH;
576           INSERT_AFTER(nt, p);
577           nt->status = ACTION;
578           nt->action = THROW;
579           nt->height = t->height;
580           nt->hand = hand;
581           nt->posn = posn;
582         }
583   }
584   return True;
585 }
586
587 /* Connnect up throws and catches to figure out which ball goes where.
588    Do the same with the juggler's hands. */
589
590 static void
591 lob(ModeInfo *mi)
592 {
593   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
594   Trajectory *t, *p;
595   int h;
596   for (t = sp->head->next; t != sp->head; t = t->next) {
597         if (t->status == ACTION) {
598 #if 0
599           (void) fprintf(stderr, (t->action == CATCH) ? "A %c%c%c\n" : "A %c%c%c%d\n",
600                           t->posn,
601                           t->hand ? 'R' : 'L',
602                           (t->action == THROW) ? 'T' : (t->action == CATCH ? 'C' : 'N'),
603                           t->height);
604 #endif
605           if (t->action == THROW) {
606                 if (t->type == Empty) {
607                   if (MI_NPIXELS(mi) > 2) {
608                         t->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
609                   }
610                   t->spin = NRAND(5) - 2;
611                   t->degree_offset = NRAND(360);
612                   t->divisions = 2 * ((LRAND() & 1) + 1);
613                 }
614
615                 /* search forward for next hand catch */
616                 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
617                   if (p->action == CATCH) {
618                         if (t->handlink == NULL && p->hand == t->hand) {
619                           t->handlink = p; /* next catch in this hand */
620                         }
621                   }
622                 }
623
624                 if (t->height > 0) {
625                   h = t->height - 1;
626
627                   /* search forward for next ball catch */
628                   for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) {
629                         if (p->action == CATCH) {
630                           if (t->balllink == NULL && --h < 1) { /* caught */
631 #if 0
632                                 if (p->type == Full) {
633                                   /* dropped */
634                                 }
635 #endif
636                                 t->balllink = p; /* complete trajectory */
637                                 p->type = Full;
638                                 p->color = t->color; /* accept catch */
639                                 p->spin = t->spin;
640                                 p->degree_offset = t->degree_offset;
641                                 p->divisions = t->divisions;
642                           }
643                         }
644                   }
645                 }
646                 t->type = Empty; /* thrown */
647           } else if (t->action == CATCH) {
648                 /* search forward for next throw from this hand */
649                 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
650                   if (p->action == THROW && p->hand == t->hand) {
651                         p->type = t->type; /* pass ball */
652                         p->color = t->color; /* pass color */
653                         p->spin = NRAND(5) - 2;
654                         p->degree_offset = NRAND(360);
655                         p->divisions = 2 * ((LRAND() & 1) + 1);
656                         t->handlink = p;
657                   }
658                 }
659           }
660           t->status = LINKEDACTION;
661         }
662   }
663 }
664
665 /* Convert hand position symbols into actual time/space coordinates */
666 static void
667 positions(jugglestruct *sp)
668 {
669   Trajectory *t;
670   int now = 0;
671   for (t = sp->head->next; t != sp->head; t = t->next) {
672         if (t->status == PTHRATCH) {
673           now = t->start;
674         } else if (t->status == LINKEDACTION) {
675           int xo = 0, yo;
676
677           /* time */
678           if (t->action == CATCH) {
679                 if (t->type == Empty) {
680                   now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
681                 } else {
682                   now += THROW_CATCH_INTERVAL;     /* successful catch */
683                 }
684           } else {
685                 now += (int) CATCH_THROW_INTERVAL;  /* throws are very short */
686           }
687           t->start = now;
688
689           /* space */
690           yo = ARMLENGTH;
691           switch (t->posn) {
692           case '-': xo = SX - POSE; break;
693           case '_':
694           case '+': xo = SX + POSE; break;
695           case '=': xo = - SX - POSE; yo += 2 * POSE; break;
696           default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
697           }
698           t->x = sp->cx + ((t->hand == LEFT) ? xo : -xo);
699           t->y = sp->cy + yo;
700
701           t->status = PTHRATCH;
702         }
703   }
704 }
705
706
707 /* Private physics functions */
708
709 static Spline
710 makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1)
711 {
712   Spline s;
713   double a, b, c, d;
714   int x10;
715   double t10;
716
717   x10 = x1 - x0;
718   t10 = t1 - t0;
719   a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
720   b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
721   c = dx0;
722   d = x0;
723   s.a = a;
724   s.b = -3*a*t0 + b;
725   s.c = (3*a*t0 - 2*b)*t0 + c;
726   s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
727   return s;
728 }
729
730 static double
731 makeSplinePair(Spline *s1, Spline *s2,
732                            int x0, double dx0, int t0,
733                            int x1, int t1,
734                            int x2, double dx2, int t2)
735 {
736   int x10, x21;
737   double t21, t10, t20, dx1;
738   x10 = x1 - x0;
739   x21 = x2 - x1;
740   t21 = t2 - t1;
741   t10 = t1 - t0;
742   t20 = t2 - t0;
743   dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
744                  - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
745         (2*t10*t21*t20);
746   *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
747   *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
748   return dx1;
749 }
750
751 /* Turn abstract timings into physically appropriate ball trajectories. */
752 static Bool
753 projectile(jugglestruct *sp)
754 {
755   Trajectory *t, *n;
756   for (t = sp->head->next; t != sp->head; t = t->next) {
757         if (t->status != PTHRATCH) {
758           continue;
759         }
760         if (t->action == THROW) {
761           if (t->balllink != NULL) {
762                 if (t->posn == '_') { /* Bounce once */
763                   double tc, y0, yf, yc, tb, e, i;
764
765                   tc = t->balllink->start - t->start;
766
767                   yf = sp->cy + FY;
768                   y0 = t->y;
769                   yc = t->balllink->y;
770                   e = 1; /* permissible error in yc */
771
772                   /*
773                          tb = time to bounce
774                          yt = height at catch time after one bounce
775                          one or three roots according to timing
776                          find one by interval bisection
777                   */
778                   tb = tc;
779                   for(i = tc / 2; i > 0; i/=2){
780                         double dy, dt, yt;
781                         if(tb == 0){
782                           (void) fprintf(stderr, "div by zero!\n");
783                           break;
784                         }
785                         dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
786                         dt = tc - tb;
787                         yt = -COR*dy*dt + 0.5*sp->Gr*dt*dt + yf;
788                         if(yt > yc + e){
789                           tb-=i;
790                         }else if(yt < yc - e){
791                           tb+=i;
792                         }else{
793                           break;
794                         }
795                   }
796
797                   {
798                         double t0, dy;
799
800                         t->dx = (t->balllink->x - t->x) / tc;
801
802                         /* ball follows parabola down */
803                         INSERT_AFTER(n, t->prev);
804                         n->start = t->start;
805                         n->finish = (int) (t->start + tb);
806                         n->type = Ball;
807                         n->color = t->color;
808                         n->spin = t->spin;
809                         n->degree_offset = t->degree_offset;
810                         n->divisions = t->divisions;
811                         n->status = PREDICTOR;
812
813                         t->dy = (yf - y0)/tb - 0.5*sp->Gr*tb;
814                         t0 = n->start;
815                         /* Represent parabola as a degenerate spline -
816                            linear in x, quadratic in y */
817                         n->xp.a = 0;
818                         n->xp.b = 0;
819                         n->xp.c = t->dx;
820                         n->xp.d = -t->dx*t0 + t->x;
821                         n->yp.a = 0;
822                         n->yp.b = sp->Gr/2;
823                         n->yp.c = -sp->Gr*t0 + t->dy;
824                         n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
825
826
827                         /* ball follows parabola up */
828                         INSERT_AFTER(n, t->prev);
829                         n->start = (int) (t0 + tb);
830                         n->finish = (int) (t0 + tc);
831                         n->type = Ball;
832                         n->color = t->color;
833                         n->spin = t->spin;
834                         n->degree_offset = t->degree_offset;
835                         n->divisions = t->divisions;
836                         n->status = PREDICTOR;
837
838                         n->xp.a = 0;
839                         n->xp.b = 0;
840                         n->xp.c = t->dx;
841                         n->xp.d = -t->dx*t0 + t->x;
842
843                         dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
844                         t0 = n->start;
845                         /* Represent parabola as a degenerate spline -
846                            linear in x, quadratic in y */
847                         n->yp.a = 0;
848                         n->yp.b = sp->Gr/2;
849                         n->yp.c = -sp->Gr*t0 - COR*dy;
850                         n->yp.d = sp->Gr/2*t0*t0 + COR*dy*t0 + yf;
851                   }
852
853                   t->status = BPREDICTOR;
854
855                 } else {
856                   double t0, dt;
857
858                   /* ball follows parabola */
859                   INSERT_AFTER(n, t->prev);
860                   n->start = t->start;
861                   n->finish = t->balllink->start;
862                   n->type = Ball;
863                   n->color = t->color;
864                   n->spin = t->spin;
865                   n->degree_offset = t->degree_offset;
866                   n->divisions = t->divisions;
867                   n->status = PREDICTOR;
868
869                   t0 = n->start;
870                   dt = t->balllink->start - t->start;
871                   t->dx = (t->balllink->x - t->x) / dt;
872                   t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
873
874                   /* Represent parabola as a degenerate spline -
875                          linear in x, quadratic in y */
876                   n->xp.a = 0;
877                   n->xp.b = 0;
878                   n->xp.c = t->dx;
879                   n->xp.d = -t->dx*t0 + t->x;
880                   n->yp.a = 0;
881                   n->yp.b = sp->Gr/2;
882                   n->yp.c = -sp->Gr*t0 + t->dy;
883                   n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
884
885
886                   t->status = BPREDICTOR;
887                 }
888           } else { /* Zero Throw */
889                 t->status = BPREDICTOR;
890           }
891         }
892   }
893   return True;
894 }
895
896 /* Turn abstract hand motions into cubic splines. */
897 static void
898 hands(jugglestruct *sp)
899 {
900   Trajectory *t, *u, *v;
901   for (t = sp->head->next; t != sp->head; t = t->next) {
902         /* no throw => no velocity */
903         if (t->status != BPREDICTOR) {
904           continue;
905         }
906
907         u = t->handlink;
908         if (u == NULL) { /* no next catch */
909           continue;
910         }
911         v = u->handlink;
912         if (v == NULL) { /* no next throw */
913           continue;
914         }
915
916         /* double spline takes hand from throw, thru catch, to
917            next throw */
918
919         t->finish = u->start;
920         t->status = PREDICTOR;
921
922         u->finish = v->start;
923         u->status = PREDICTOR;
924
925         (void) makeSplinePair(&t->xp, &u->xp,
926                                                   t->x, t->dx, t->start,
927                                                   u->x, u->start,
928                                                   v->x, v->dx, v->start);
929         (void) makeSplinePair(&t->yp, &u->yp,
930                                                   t->y, t->dy, t->start,
931                                                   u->y, u->start,
932                                                   v->y, v->dy, v->start);
933
934         t->status = PREDICTOR;
935   }
936 }
937
938 /* Given target x, y find_elbow puts hand at target if possible,
939  * otherwise makes hand point to the target */
940 static void
941 find_elbow(jugglestruct *sp, XPoint *h, XPoint *e, int x, int y, int z)
942 {
943   double r, h2, t;
944
945   h2 = x*x + y*y + z*z;
946   if (h2 > 4*ARMLENGTH*ARMLENGTH) {
947         t = ARMLENGTH/sqrt(h2);
948         e->x = (short) (t*x);
949         e->y = (short) (t*y);
950         h->x = 2 * e->x;
951         h->y = 2 * e->y;
952   } else {
953         r = sqrt((double)(x*x + z*z));
954         t = sqrt(4 * ARMLENGTH * ARMLENGTH / h2 - 1);
955         e->x = (short) (x*(1 - y*t/r)/2);
956         e->y = (short) ((y + r*t)/2);
957         h->x = x;
958         h->y = y;
959   }
960 }
961
962 /* NOTE: returned x, y adjusted for arm reach */
963 static void
964 draw_arm(ModeInfo * mi, Hand side, int *x, int *y)
965 {
966   Display *dpy = MI_DISPLAY(mi);
967   Window win = MI_WINDOW(mi);
968   GC gc = MI_GC(mi);
969   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
970
971   int sig = (side == LEFT) ? 1 : -1;
972
973   XSetLineAttributes(dpy, gc,
974                 ARMWIDTH, LineSolid, CapRound, JoinRound);
975   if (sp->arm[side][0].x != *x || sp->arm[side][0].y != *y) {
976         XPoint h, e;
977         XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi));
978         find_elbow(sp, &h, &e, *x - sig*SX - sp->cx, *y - SY - sp->cy, SZ);
979         XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
980         *x = sp->arm[side][0].x = sp->cx + sig*SX + h.x;
981         *y = sp->arm[side][0].y = sp->cy + SY + h.y;
982         sp->arm[side][1].x = sp->cx + sig*SX + e.x;
983         sp->arm[side][1].y = sp->cy + SY + e.y;
984   }
985   XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
986   XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
987   XSetLineAttributes(dpy, gc,
988                 1, LineSolid, CapNotLast, JoinRound);
989 }
990
991 static void
992 draw_figure(ModeInfo * mi)
993 {
994   Display *dpy = MI_DISPLAY(mi);
995   Window win = MI_WINDOW(mi);
996   GC gc = MI_GC(mi);
997   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
998
999   XSetLineAttributes(dpy, gc,
1000                 ARMWIDTH, LineSolid, CapRound, JoinRound);
1001   XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1002   XDrawLines(dpy, win, gc, sp->figure_path, FIGURE1, CoordModeOrigin);
1003   XDrawSegments(dpy, win, gc, sp->figure_segs, FIGURE2);
1004   XDrawArc(dpy, win, gc,
1005          sp->cx - HED/2, sp->cy + NEY - HED, HED, HED, 0, 64*360);
1006   XSetLineAttributes(dpy, gc,
1007                 1, LineSolid, CapNotLast, JoinRound);
1008 }
1009
1010
1011 /* dumps a human-readable rendition of the current state of the juggle
1012    pipeline to stderr for debugging */
1013 #ifdef OLDDEBUG
1014 static void
1015 dump(jugglestruct *sp)
1016 {
1017   Trajectory *t;
1018
1019   for (t = sp->head->next; t != sp->head; t = t->next) {
1020         switch (t->status) {
1021         case THROW:
1022           (void) fprintf(stderr, "T %c%d\n", t->posn, t->height);
1023           break;
1024         case ACTION:
1025           (void) fprintf(stderr, t->action == CATCH?"A %c%c%c\n":"A %c%c%c%d\n",
1026                           t->posn,
1027                           t->hand ? 'R' : 'L',
1028                           (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1029                           t->height);
1030           break;
1031         case LINKEDACTION:
1032           (void) fprintf(stderr, "L %c%c%c%d %d\n",
1033                           t->posn,
1034                           t->hand?'R':'L',
1035                           (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1036                           t->height, t->color);
1037           break;
1038         case PTHRATCH:
1039           (void) fprintf(stderr, "O %c%c%c%d %d %2d %6d %6d\n", t->posn,
1040                           t->hand?'R':'L',
1041                           (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1042                           t->height, t->type, t->color,
1043                           t->start, t->finish);
1044           break;
1045         case PREDICTOR:
1046           (void) fprintf(stderr, "P %c      %2d %6d %6d %g\n",
1047                           t->type == Ball?'b':t->type == Empty?'e':'f',
1048                           t->color,
1049                           t->start, t->finish, t->yp.c);
1050           break;
1051         default:
1052           (void) fprintf(stderr, "status %d not implemented\n", t->status);
1053           break;
1054         }
1055   }
1056 }
1057 #endif
1058
1059 static int get_num_balls(const char *j)
1060 {
1061   int balls = 0;
1062   const char *p;
1063   int h = 0;
1064   for (p = j; *p; p++) {
1065         if (*p >= '0' && *p <='9') { /* digit */
1066           h = 10*h + (*p - '0');
1067         } else {
1068           if (h > balls) {
1069                 balls = h;
1070           }
1071           h = 0;
1072         }
1073   }
1074   return balls;
1075 }
1076
1077 #ifdef __cplusplus
1078 extern "C" {
1079 #endif
1080
1081 static int compare_num_balls(const void *p1, const void *p2)
1082 {
1083   int i = get_num_balls(((patternstruct*)p1)->pattern);
1084   int j = get_num_balls(((patternstruct*)p2)->pattern);
1085   if (i > j) {
1086         return (1);
1087   } else if (i < j) {
1088         return (-1);
1089   } else {
1090         return (0);
1091   }
1092 }
1093
1094 #ifdef __cplusplus
1095 }
1096 #endif
1097
1098 /* Public functions */
1099
1100 void
1101 release_juggle(ModeInfo * mi)
1102 {
1103         if (juggles != NULL) {
1104                 int screen;
1105
1106                 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1107                         free_juggle(&juggles[screen]);
1108                 (void) free((void *) juggles);
1109                 juggles = (jugglestruct *) NULL;
1110         }
1111         if (patternindex != NULL) {
1112                 (void) free((void *) patternindex);
1113                 patternindex = (PatternIndex *) NULL;
1114         }
1115 }
1116
1117 void
1118 init_juggle(ModeInfo * mi)
1119 {
1120         jugglestruct *sp;
1121         int i;
1122         XPoint figure1[FIGURE1];
1123         XSegment figure2[FIGURE2];
1124         if (pattern != NULL && *pattern == '.') {
1125           pattern = NULL;
1126         }
1127         if (pattern == NULL && patternindex == NULL) {
1128           /* pattern list needs indexing */
1129           int nelements = sizeof(portfolio)/sizeof(patternstruct);
1130           int maxballs;
1131           int numpat = 0;
1132
1133           /* sort according to number of balls */
1134           qsort((void*)portfolio, nelements,
1135                         sizeof(patternstruct), compare_num_balls);
1136
1137           /* last pattern has most balls */
1138           maxballs = get_num_balls(portfolio[nelements - 1].pattern);
1139           /* allocate index */
1140           if ((patternindex = (PatternIndex *) calloc(maxballs + 1,
1141                                 sizeof (PatternIndex))) == NULL) {
1142                 return;
1143           }
1144
1145           /* run through sorted list, indexing start of each group
1146                  and number in group */
1147           maxballs = 1;
1148           for (i = 0; i < nelements; i++) {
1149                 int b = get_num_balls(portfolio[i].pattern);
1150                 if (b > maxballs) {
1151                   if (MI_IS_VERBOSE(mi)) {
1152                         (void) fprintf(stderr, "%d %d ball pattern%s\n",
1153                                         numpat, maxballs, (numpat == 1) ? "" : "s");
1154                   }
1155                   patternindex[maxballs].number = numpat;
1156                   maxballs = b;
1157                   numpat = 1;
1158                   patternindex[maxballs].start = i;
1159                 } else {
1160                   numpat++;
1161                 }
1162           }
1163           if (MI_IS_VERBOSE(mi)) {
1164                 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1165                                 numpat, maxballs, (numpat == 1) ? "" : "s");
1166           }
1167           patternindex[maxballs].number = numpat;
1168         }
1169
1170         if (juggles == NULL) { /* allocate jugglestruct */
1171                 if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi),
1172                                sizeof (jugglestruct))) == NULL) {
1173                         release_juggle(mi);
1174                         return;
1175                 }
1176         }
1177         sp = &juggles[MI_SCREEN(mi)];
1178
1179         sp->count = 0;
1180
1181         if (MI_IS_FULLRANDOM(mi)) {
1182                 sp->solid = (Bool) (LRAND() & 1);
1183 #ifdef UNI
1184                 sp->uni = (Bool) (LRAND() & 1);
1185 #endif
1186         } else {
1187                 sp->solid = solid;
1188 #ifdef UNI
1189                 sp->uni = uni;
1190 #endif
1191         }
1192
1193         sp->width = MI_WIDTH(mi);
1194         sp->height = MI_HEIGHT(mi);
1195         sp->count = ABS(MI_COUNT(mi));
1196         if (sp->count == 0)
1197                 sp->count = 150;
1198         sp->scale = sp->height / 480.0;
1199         /* vary x a little so the juggler does not burn the screen */
1200         sp->cx = sp->width / 2 + RFX + NRAND(LFX - RFX + 1);
1201         sp->cy = sp->height - FY - ((int) sp->uni) * FY / 3; /* raise higher */
1202         /* "7" hits top of screen */
1203         sp->Gr = GRAVITY(sp->cy, 7 * THROW_CATCH_INTERVAL);
1204
1205         figure1[0].x = LHIPX, figure1[0].y = HIPY;
1206         figure1[1].x = 0, figure1[1].y = WSTY;
1207         figure1[2].x = SX, figure1[2].y = SY;
1208         figure1[3].x = -SX, figure1[3].y = SY;
1209         figure1[4].x = 0, figure1[4].y = WSTY;
1210         figure1[5].x = RHIPX, figure1[5].y = HIPY;
1211         figure1[6].x = LHIPX, figure1[6].y = HIPY;
1212         figure2[0].x1 = 0, figure2[0].y1 = SY,
1213           figure2[0].x2 = 0, figure2[0].y2 = NEY;
1214         figure2[1].x1 = LHIPX, figure2[1].y1 = HIPY,
1215           figure2[1].x2 = LFX, figure2[1].y2 = FY;
1216         figure2[2].x1 = RHIPX, figure2[2].y1 = HIPY,
1217           figure2[2].x2 = RFX, figure2[2].y2 = FY;
1218
1219         /* Body Path */
1220         for (i = 0; i <  FIGURE1; i++) {
1221           sp->figure_path[i].x = figure1[i].x + sp->cx;
1222           sp->figure_path[i].y = figure1[i].y + sp->cy;
1223         }
1224         /* Body Segments */
1225         for (i = 0; i < FIGURE2; i++) {
1226           sp->figure_segs[i].x1 = figure2[i].x1 + sp->cx;
1227           sp->figure_segs[i].y1 = figure2[i].y1 + sp->cy;
1228           sp->figure_segs[i].x2 = figure2[i].x2 + sp->cx;
1229           sp->figure_segs[i].y2 = figure2[i].y2 + sp->cy;
1230         }
1231         /* Shoulders */
1232         sp->arm[LEFT][2].x = sp->cx + SX;
1233         sp->arm[LEFT][2].y = sp->cy + SY;
1234         sp->arm[RIGHT][2].x = sp->cx - SX;
1235         sp->arm[RIGHT][2].y = sp->cy + SY;
1236
1237         if (sp->trace == NULL) {
1238           if ((sp->trace = (XPoint *)calloc(trail, sizeof(XPoint))) == NULL) {
1239                 free_juggle(sp);
1240                 return;
1241           }
1242         }
1243
1244         /* Clear the background. */
1245         MI_CLEARWINDOW(mi);
1246
1247         draw_figure(mi);
1248
1249         /* record start time */
1250         sp->begintime = time(NULL);
1251
1252         free_pattern(sp);
1253
1254         /* create circular list */
1255         INSERT_AFTER_TOP(sp->head, sp->head);
1256
1257         /* generate pattern */
1258         if (pattern == NULL) {
1259
1260 #define MAXPAT 10
1261 #define MAXREPEAT 30
1262 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
1263 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
1264
1265           int count = 0;
1266           int num_balls = MINBALLS + NRAND(MAXBALLS - MINBALLS);
1267           while (count < MI_CYCLES(mi)) {
1268                 char buf[MAXPAT * 3 + 3], *b = buf;
1269                 int maxseen = 0;
1270                 int l = NRAND(MAXPAT) + 1;
1271                 int t = NRAND(MAXREPEAT) + 1;
1272
1273                 { /* vary number of balls */
1274                   int new_balls = num_balls;
1275                   int change;
1276
1277                   if (new_balls == 2) /* Do not juggle 2 that often */
1278                     change = NRAND(2 + CHANGE_BIAS / 4);
1279                   else
1280                     change = NRAND(2 + CHANGE_BIAS);
1281                   switch (change) {
1282                   case 0:
1283                         new_balls++;
1284                         break;
1285                   case 1:
1286                         new_balls--;
1287                         break;
1288                   default:
1289                         break; /* NO-OP */
1290                   }
1291                   if (new_balls < MINBALLS) {
1292                         new_balls += 2;
1293                   }
1294                   if (new_balls > MAXBALLS) {
1295                         new_balls -= 2;
1296                   }
1297                   if (new_balls < num_balls) {
1298                         if (!program(mi, "[*]", 1)) /* lose ball */
1299                                 return;
1300                   }
1301                   num_balls = new_balls;
1302                 }
1303                 count++;
1304
1305                 if (NRAND(2) && patternindex[num_balls].number) {
1306                   /* Pick from PortFolio */
1307                   if (!program(mi,
1308                           portfolio[patternindex[num_balls].start +
1309                                   NRAND(patternindex[num_balls].number)].pattern,
1310                                   t))
1311                         return;
1312                 } else {
1313                   /* Invent a new pattern */
1314                   *b++='[';
1315                   for(i = 0; i < l; i++){
1316                         int n, m;
1317                         do { /* Triangular Distribution => high values more likely */
1318                           m = NRAND(num_balls + 1);
1319                           n = NRAND(num_balls + 1);
1320                         } while(m >= n);
1321                         if (n == num_balls) {
1322                           maxseen = 1;
1323                         }
1324                         switch(NRAND(6 + POSITION_BIAS)){
1325                         case 0:            /* Inside throw */
1326                           *b++ = '-'; break;
1327                         case 1:            /* Outside throw */
1328                           *b++ = '+'; break;
1329                         case 2:            /* Cross throw */
1330                           *b++ = '='; break;
1331                         case 3:            /* Cross catch */
1332                           *b++ = '&'; break;
1333                         case 4:            /* Cross throw and catch */
1334                           *b++ = 'x'; break;
1335                         case 5:            /* Bounce */
1336                           *b++ = '_'; break;
1337                         default:
1338                           break; /* NO-OP */
1339                         }
1340
1341                         *b++ = n + '0';
1342                         *b++ = ' ';
1343                   }
1344                   *b++ = ']';
1345                   *b = '\0';
1346                   if (maxseen) {
1347                         if (!program(mi, buf, t))
1348                                 return;
1349                   }
1350                 }
1351           }
1352         } else { /* pattern supplied in height or 'a' notation */
1353           if (!program(mi, pattern, MI_CYCLES(mi)))
1354                 return;
1355         }
1356
1357         adam(sp);
1358
1359         if (!part(sp))
1360                 return;
1361
1362         lob(mi);
1363
1364         positions(sp);
1365
1366         if (!projectile(sp))
1367                 return;
1368
1369         hands(sp);
1370
1371 #ifdef OLDDEBUG
1372         dump(sp);
1373 #endif
1374 }
1375
1376 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1377
1378 #ifdef SUNOS4
1379 /*-
1380  * Workaround SunOS 4 framebuffer bug which causes balls to leave dust
1381  * trace behind when erased
1382  */
1383 #define ERASE_BALL(x,y) \
1384         XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1385         XFillArc(dpy, window, gc, \
1386                 (x) - BALLRADIUS - 2, (y) - BALLRADIUS - 2, \
1387                 2*(BALLRADIUS + 2), 2*(BALLRADIUS + 2), 0, 23040)
1388 #else
1389
1390 #define ERASE_BALL(x,y) \
1391         XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1392         XFillArc(dpy, window, gc, \
1393                 (x) - BALLRADIUS, (y) - BALLRADIUS, \
1394                 2*BALLRADIUS, 2*BALLRADIUS, 0, 23040)
1395 #endif
1396
1397 static void
1398 draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions)
1399 {
1400         Display    *dpy = MI_DISPLAY(mi);
1401         Window      window = MI_WINDOW(mi);
1402         GC          gc = MI_GC(mi);
1403         jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1404         int offset;
1405
1406         XSetForeground(dpy, gc, color);
1407         if ((color == MI_WHITE_PIXEL(mi)) ||
1408             ((divisions != 2) && (divisions != 4)) || sp->solid) {
1409                 XFillArc(dpy, window, gc,
1410                         x - BALLRADIUS, y - BALLRADIUS,
1411                         2*BALLRADIUS, 2*BALLRADIUS,
1412                         0, 23040);
1413                 return;
1414         }
1415         offset = (int) (degree_offset * 64);
1416         if (divisions == 4) { /* 90 degree divisions */
1417                 XFillArc(dpy, window, gc,
1418                         x - BALLRADIUS, y - BALLRADIUS,
1419                         2*BALLRADIUS, 2*BALLRADIUS,
1420                         offset, 5760);
1421                 XFillArc(dpy, window, gc,
1422                         x - BALLRADIUS, y - BALLRADIUS,
1423                         2*BALLRADIUS, 2*BALLRADIUS,
1424                         (offset + 11520) % 23040, 5760);
1425                 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1426                 XFillArc(dpy, window, gc,
1427                         x - BALLRADIUS, y - BALLRADIUS,
1428                         2*BALLRADIUS, 2*BALLRADIUS,
1429                         (offset + 5760) % 23040, 5760);
1430                 XFillArc(dpy, window, gc,
1431                         x - BALLRADIUS, y - BALLRADIUS,
1432                         2*BALLRADIUS, 2*BALLRADIUS,
1433                         (offset + 17280) % 23040, 5760);
1434         } else { /* 180 degree divisions */
1435                 XFillArc(dpy, window, gc,
1436                         x - BALLRADIUS, y - BALLRADIUS,
1437                         2*BALLRADIUS, 2*BALLRADIUS,
1438                         offset, 11520);
1439                 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1440                 XFillArc(dpy, window, gc,
1441                         x - BALLRADIUS, y - BALLRADIUS,
1442                         2*BALLRADIUS, 2*BALLRADIUS,
1443                         (offset + 11520) % 23040, 11520);
1444         }
1445         XFlush(dpy);
1446 }
1447
1448 void
1449 draw_juggle(ModeInfo * mi)
1450 {
1451         Display    *dpy = MI_DISPLAY(mi);
1452         Window      window = MI_WINDOW(mi);
1453         GC          gc = MI_GC(mi);
1454         Trajectory *traj;
1455         int future = 0;
1456         int length = 0;
1457         jugglestruct *sp;
1458
1459         if (juggles == NULL)
1460                 return;
1461         sp = &juggles[MI_SCREEN(mi)];
1462         if (sp->trace == NULL)
1463                 return;
1464
1465         MI_IS_DRAWN(mi) = True;
1466
1467         draw_figure(mi);
1468
1469         {
1470           struct timeval tv;
1471 # ifdef GETTIMEOFDAY_TWO_ARGS
1472       struct timezone tzp;
1473           gettimeofday(&tv, &tzp);
1474 # else
1475           gettimeofday(&tv);
1476 # endif
1477
1478           sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
1479         }
1480         for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
1481           length++;
1482           if (traj->status != PREDICTOR) {
1483                 continue;
1484           }
1485           if (traj->start > future) {
1486                 future = traj->start;
1487           }
1488           if (sp->time < traj->start) { /* early */
1489                 continue;
1490           } else if (sp->time < traj->finish) { /* working */
1491                 int x = (int) CUBIC(traj->xp, sp->time);
1492                 int y = (int) CUBIC(traj->yp, sp->time);
1493                 unsigned long color;
1494
1495                 if (MI_NPIXELS(mi) > 2) {
1496                   color = MI_PIXEL(mi, traj->color);
1497                 } else {
1498                   color = MI_WHITE_PIXEL(mi);
1499                 }
1500                 if (traj->type == Empty || traj->type == Full) {
1501                   draw_arm(mi, traj->hand, &x, &y);
1502                 }
1503                 if (traj->type == Ball || traj->type == Full) {
1504                   if(trail > 0) {
1505                         ERASE_BALL(sp->trace[sp->traceindex].x,
1506                                 sp->trace[sp->traceindex].y);
1507                         sp->trace[sp->traceindex].x = traj->x;
1508                         sp->trace[sp->traceindex].y = traj->y;
1509                         if (++sp->traceindex >= trail) {
1510                           sp->traceindex = 0;
1511                         }
1512                   } else {
1513                         ERASE_BALL(traj->x, traj->y);
1514                   }
1515                   draw_juggle_ball(mi, color, x, y, traj->degree_offset, traj->divisions);
1516                   traj->degree_offset = traj->degree_offset +
1517                     SPIN_DEGREES * traj->spin / sp->count;
1518                   if (traj->degree_offset < 0.0)
1519                         traj->degree_offset += 360.0;
1520                   else if (traj->degree_offset >= 360.0)
1521                         traj->degree_offset -= 360.0;
1522                 }
1523                 traj->x = x;
1524                 traj->y = y;
1525           } else { /* expired */
1526                 Trajectory *n = traj;
1527
1528                 ERASE_BALL(traj->x, traj->y);
1529                 traj=traj->prev;
1530                 REMOVE(n);
1531           }
1532         }
1533
1534         /*** FIXME-BEGIN ***/
1535         /* pattern generator would refill here when necessary */
1536 #if 1
1537         if (future == 0) {
1538 #else
1539         if (sp->count > MI_CYCLES(mi)) { /* pick a new juggle */
1540 #endif
1541                         init_juggle(mi);
1542         }
1543         /*** FIXME-END ***/
1544
1545 }
1546
1547 #endif /* MODE_juggle */