## Juggler

Keith Vetter 2002-11-25 - This is from an old graphics class I took back in 1994. It shows a person juggling from 3 to 20 balls in either the cascade or even ball juggling pattern.

I added the juggling man just last night--with the design borrowed from Flag signalling.

The goal of this program was not just to show a perfect juggler--although if you turn on the perfect button it will do that. Rather, the goal was to add errors into each throw and see how the juggler responds. I've remove much of that code, but still each throw is unique, based on how fast the ball must be released before the next ball lands.

One undocumented feature is that you can use the up and down arrows to zoom in and out.

KBK Lovely! Now if we can get Vince Darley to add site swap notation (http://www.juggling.org/help/siteswap/ ), we'll be all set.

Vince Ok, you've tempted me ;-)

AM If I remember correctly, the physics behind juggling is not at all trivial - you need a certain rhythmic motion which is difficult to learn. I do not remember the details, unfortunately.

```##+################################################################
#
# tkjuggler.tcl -- an interactive juggling program.
# by Keith P. Vetter
#
# Revisions:
# KPV Nov, 1994 - initial revision for UCB CS285, Fall 1994
# KPV Nov 25, 2002 - removed 3d YART support; added juggler
#

package require Tk

##+################################################################
#
# Juggle -- controls the animation. Probable should rewrite using after to
# avoid the update call, perhaps later.
#
proc juggle {{delta 1}} {
global ss

while {1} {                                 ;# Go until button press
incr ss(t) \$delta                       ;# Another clock tick
for {set j 0} {\$j < \$ss(num)} {incr j} {;# For each ball
move_ball \$ss(t) \$j                 ;# Move it
}
move_hands

update                                  ;# Show it on the screen
if {\$ss(stop) || \$delta == 0} return    ;# Should we stop
}
}
##+###############################################################
#
# Init - Initializes all non-varying state variables
#
proc init {} {
global ss

set ss(pattern)     cascade                 ;# Juggling pattern
set ss(perfect)     0                       ;# Add randomness

set ss(stop)        1                       ;# Animation on/off
set ss(h)           300                     ;# Height
set ss(flight)      64
set ss(num)         5
set ss(v,h)         \$ss(h)                  ;# Display variants of above
set ss(v,flight)    \$ss(flight)
set ss(v,num)       \$ss(num)
set ss(w)           140
set ss(s)           40                      ;# Size of the ball
set ss(s2)          [expr {\$ss(s)/2}]       ;# Half the size of the ball
set ss(startstop)   Start
set ss(scale)       1

set ss(colors)      {magenta2 orange2 MediumPurple2 orchid2 SpringGreen2}
lappend ss(colors)  lightslateblue PaleVioletRed2 chartreuse DarkOrchid2
lappend ss(colors)  lightslateblue PaleVioletRed2 chartreuse DarkOrchid2
lappend ss(colors)  purple2 cyan2 goldenrod2 plum2 HotPink2 deepskyblue
lappend ss(colors)  firebrick2 slateblue1 maroon2 DarkGoldenrod2
lappend ss(colors)  coral2 thistle2 skyblue2
}
##+###############################################################
#
# Display - Sets up the display with its canvas and sliders
#
proc display {} {
global ss

foreach w [winfo child .] {                 ;# Delete any existing widgets
destroy \$w
}
set ss(ch) [expr [winfo screenheight .] - 300];# Canvas height
set ss(cw) 664                              ;# Canvas width
frame .ftop
frame .fbot
pack .fbot -side bottom -fill x
pack .ftop -side top -expand 1 -fill both
catch {image create photo ::img::blank -width 1 -height 1}
make_canvas

frame .fstop -relief sunken -bd 1
button .stop -textvariable ss(startstop) -command startstop -width 5
frame .fqbtn -relief sunken -bd 1
button .qbtn -text { Quit } -command exit
pack .fqbtn .fstop -side right -expand yes -in .fbot
pack .stop -in .fstop -side left -expand yes -padx 3m -pady 2m
pack .qbtn -in .fqbtn -side left -expand yes -padx 3m -pady 2m
bind .stop <2> juggle                       ;# Single step

scale .s1 -label Height -orient horizontal -from 1 -to 1000
.s1 config -relief ridge -showvalue 1 -variable ss(v,h)
scale .s2 -label "Flight Time" -orient horizontal -from 1 -to 500
.s2 config -relief ridge -showvalue 1 -variable ss(v,flight)
scale .s4 -label Balls -orient horizontal -from 3 -to 20
.s4 config -relief ridge -showvalue 1 -variable ss(v,num)

pack .s1 .s2 .s4 -side left -in .fbot
bind .s1  <ButtonRelease-1> "set_value height"
bind .s2  <ButtonRelease-1> "set_value flight"
bind .s4  <ButtonRelease-1> "set_value balls"

frame .frb                                  ;# Radiobuttons for patterns
-value cascade -command reinit -anchor w
radiobutton .shower -text "Shower" -var ss(pattern) \
-value shower -command reinit -anchor w
radiobutton .even -text "Even" -var ss(pattern) \
-value even -command reinit -anchor w
pack .frb -side left -in .fbot -padx 1
pack .cascade .even -in .frb -side top -expand yes -anchor w -fill x

frame .fcb                                  ;# Checkbuttons for options
checkbutton .crandom -text "Perfect" -var ss(perfect) -anc w
checkbutton .cback -text "Outside" -var ss(back) -command reinit -anc w
pack .fcb -side left -in .fbot -padx 1
pack .crandom .cback -in .fcb -side top -expand no -anchor w -fill x
place .about -in .fbot -relx 1 -rely 0 -anchor ne

wm withdraw .                               ;# Update to get sizes
wm geom . +0+0
wm deiconify .
wm title . "Tk Juggler"
}
##+#####################################################
#
# Make_canvas - Creates the canvas on which all output will be done
#
proc make_canvas {} {
global ss

scrollbar .vscroll -relief sunken -command ".c yview"
set c2 [expr {\$ss(cw) / 2}]
canvas .c -relief raised -borderwidth 2 -height \$ss(ch) -width \$ss(cw) \
-bg steelblue3 -highlightthickness 0
.c config -scrollregion [list -\$c2 -1200 \$c2 500]
.c config -yscrollcommand ".vscroll set" -yscrollincrement 1
.c config -highlightcolor [.c cget -bg]
.c yview moveto .4
flagman                                     ;# Draws are flagman
wink 0

pack .vscroll -in .ftop -side right -fill y
pack .c -in .ftop -fill both -expand 1

bind .c <2> ".c scan mark %x %y"
bind .c <B2-Motion> ".c scan drag %x %y"
bind .c <MouseWheel> {%W yview scroll [expr {- (%D / 120) * 20}] units}
bind .c <Configure> {Recenter %W %h %w}
bind . <Up>   {scaler 1}
bind . <Down> {scaler 0}
focus .c                                    ;# So mouse wheel works
}
##+######################################################
#
# Recenter - Called when window gets resized.
#
proc Recenter {W h w} {
set h [expr {\$h / 2.0}] ; set w [expr {\$w / 2.0}]
\$W config -scrollregion [list -\$w -1200 \$w 500]
}

##+#####################################################
#
# Move_ball - Moves ball WHO to parameter value T. It flies in a
# parabola going through points (-w,0), (0,height), (w,0).
#
proc move_ball {t w} {
global ball ss

switch \$ball(\$w,ss) {
"LR" {                                  ;# Left to right toss
place_obj ball\$w [tossing \$t \$w]

if {\$t >= \$ball(\$w,catch)} {        ;# ...just got caught
catch_ball \$w 1
set ball(\$w,ss) HR
}
}
"HR" {                                  ;# Hold in right hand
if {\$t >= \$ball(\$w,toss)} {         ;# ...just got tossed
set ball(\$w,ss) \$ss(HR)
toss_ball \$w 1
place_obj ball\$w [tossing \$t \$w]
}
}
"RL" {                                  ;# Right to left toss
place_obj ball\$w [tossing \$t \$w]

if {\$t == \$ball(\$w,catch)} {        ;# ...just got caught
catch_ball \$w 0
set ball(\$w,ss) HL
}
}
"HL" {                                  ;# Hold in left hand
if {\$t >= \$ball(\$w,toss)} {         ;# ...just got tossed
set ball(\$w,ss) \$ss(HL)
toss_ball \$w 0
place_obj ball\$w [tossing \$t \$w]
}
}
"SL" {                                  ;# Start in left hand
place_obj ball\$w [tossing \$ball(\$w,toss) \$w]
set ball(\$w,ss) "HL"
}
"SR" {                                  ;# Start in right hand
place_obj ball\$w [tossing \$ball(\$w,toss) \$w]
set ball(\$w,ss) "HR"
}
}
}
##+#####################################################
#
# Tossing - Figures out the path for a ball: x,y
#
# u = (2t/sqrt(k)*f - 1)
# x = w*u
# y = kh * (1 - u^2)
#
proc tossing {time who} {
global ball ss

set t [expr {\$time - \$ball(\$who,toss)}]     ;# Time since the toss
set f \$ball(\$who,flight)                    ;# Flight time

set u [expr {-1 + 2.0 * \$t / \$f}]           ;# range -1 to 1
set x [expr {\$ball(\$who,x) + ((1 + \$u)/2) * \$ball(\$who,w)}]
set y [expr {-(\$ball(\$who,kh) * (1 - \$u * \$u))}];# Y is a parabola

return [list \$x \$y]
}
##+#####################################################
#
# Create_hand - Creates a new hand, and put them anywhere on the canvas
#
proc create_hand {name} {
global ss

.c delete hand\$name
.c create arc 0 -\$ss(s) \$ss(s) \$ss(s) -fill orange -outline orange \
-tag "hands hand\$name hand_x\$name" -start 0 -extent -180
foreach {x1 y1 x2 y2} [.c bbox hand_x\$name] break
set color [lindex [.c config -bg] 4]        ;# Erasure color
.c create arc \$x1 0 \$x2 \$ss(s) -fill \$color -outline "" \
-tag "hands hand\$name hand_y\$name" -start 0 -extent -180
.c lower hand\$name
.c lower flagman
place_obj hand_x\$name {0 0} -1
place_obj hand_y\$name {0 0} -1
}
##+#####################################################
#
# Create_ball - Creates a new ball
#
proc create_ball {n} {
global ss

.c delete ball\$n
set color [lindex \$ss(colors) 0]            ;# Take head of the list
set ss(colors) "[lrange \$ss(colors) 1 end] \$color" ;# Put head at end
.c create oval -\$ss(s2) -\$ss(s2) \$ss(s2) \$ss(s2) -fill \$color \
-tag "balls ball\$n"
#.c create text 0 0 -text \$n -tag "balls ball\$n" -anchor c
}
##+#####################################################
#
# New_balls - Deletes then recreates the balls
#
proc new_balls {} {
global ss

.c delete balls
for {set i 0} {\$i < \$ss(num)} {incr i} {
create_ball \$i
}
juggle 0                                    ;# Update the display
}
##+#####################################################
#
# Startstop - Manipulates the start / stop button
#
proc startstop {} {
global ss

if {\$ss(startstop) == "Start"} {
set ss(startstop) "Stop"
set ss(stop) 0
after 1 juggle
} else {
set ss(startstop) "Start"
set ss(stop) 1
}
}
##+#####################################################
#
# Set_value
#
# Handles changing the values of any juggling parameter. We late-bind
# so we only change on button release.
#
proc set_value who {
global ss

if {\$who == "height"} {
set ss(h) \$ss(v,h)                      ;# Just get the height
return
} elseif {\$who == "flight"} {
set ss(flight) \$ss(v,flight)            ;# Get new flight time
} elseif {\$who == "balls"} {
set ss(num) \$ss(v,num)                  ;# New number of balls
}
reinit                                      ;# Update global values
}
##+#####################################################
#
# Reinit -- Initializes the ss variables for the balls
#
proc reinit {} {
global ss

set ss(t) 0                                 ;# Start at time 0
set ss(h) \$ss(v,h)                          ;# Height
set ss(flight) \$ss(v,flight)                ;# Flight time
set ss(num) \$ss(v,num)                      ;# How many balls
set ss(w) [expr {round(\$ss(scale) * 140)}]  ;# Width of hands
.cback config -state normal

set ss(LR) HR                               ;# State transitions
set ss(RL) HL
set ss(HR) RL                               ;# Even does weirdness
set ss(HL) LR

\$ss(pattern)                                ;# Set up for given pattern
}
##+#####################################################
#
# Best - Sets up the hold time for N balls
#
# empty (e) =   P3   -    BALL(n-1)
#           = (2f+h) - (n-1)(2f+2h)/n
#           = (2f - h(n-2)) / n
# hold (h)  = (2f - en)  / (n-2)
#
# Also h + e = time between balls = (2f+2h)/n
#
# Constraints: at the start the last ball must be in the air
#     BALL(n-1)  < P3
#  ==>     hold  < 2f / (n-2)
#  ==>     empty < 2f / n
#  alt. hold time less than time between balls
#     hold       < (2f+2h)/n
#     hold       < 2f / (n-2)
#
# Best: e == h ==> h = f / (n-1)
#
# To compute: the last ball starts at the exact moment when the first ball
# is launched. The hand is empty until the ball lands.
#
proc best {} {
global ss

set ss(hold) \$ss(flight)
if {\$ss(num) > 1} {
set ss(hold) [expr {round(1.0 * \$ss(flight) / (\$ss(num) - 1))}]
}
}
##+#####################################################
#
# Move_hands - Positions the hands correctly.
#
proc move_hands {} {
global hand ss

if {[.c find withtag hands] == ""} return  ;# No hands, do nothing
.c delete arms
foreach h {0 1} {
set where [where_hands \$ss(t) \$h]       ;# Where it belongs
foreach {x y} \$where break
set x [expr {\$x - 1 - \$h}]              ;# Fudge factor
set y [expr {\$y - 1}]                   ;# Fudge factor
place_obj hand\$h [list \$x \$y] -1        ;# Put into place

set w [expr {3 * \$ss(s) / 4}]
set y [expr {\$y + \$w}]
.c create line \$ss(elbowx,\$h) \$ss(elbowy,\$h) \$x \$y -tag arms \
-fill gray95 -width \$w

if {\$hand(\$h,ss) == "full"} {           ;# Does it have a ball in it?
set b ball\$hand(\$h,ball)            ;# Yep, then move the ball also
place_obj \$b \$where
}
}
.c lower arms hands
}
##+#####################################################
#
# Where_hands - Determines where H hand should be at time T
#
proc where_hands {t h} {
global hand ss

set d [expr {\$hand(\$h,duration) - 1}]
if {\$d <= 0} {set d 1}

if {\$hand(\$h,ss) == "full"} {
set p [expr {1.0 - (1.0*\$hand(\$h,toss) - \$t -1) / \$d}]
set y [expr {\$hand(\$h,y) - \$ss(s) * (4 * (\$p * (\$p - 1)))}]
} else {
set p [expr {(1.0 * \$hand(\$h,catch) - \$t) / \$hand(\$h,duration)}]
set y [expr {\$ss(s2) * (4 * (\$p * (\$p - 1)))}]
}
set w [expr {\$ss(w) + \$ss(shift)}]          ;# Biggest width

if \$h {                                     ;# X depends on which hand
set x [expr  {\$w - 2 * \$p * \$ss(shift)}]
} else {
set x [expr {-\$w + 2 * \$p * \$ss(shift)}]
}

set x [expr {round(\$x)}]
set y [expr {round(\$y)}]
return [list \$x \$y]
}
##+#####################################################
#
# Adjust - Adjust the flight & hold time so that their sum is a
# multiple of the number of balls. This way, we get no round off
# errors in computing where the balls should start.
#
global ss

set n \$ss(num)                              ;# Number of balls
set f \$ss(flight)                           ;# Flight time
set h \$ss(hold)                             ;# Hold time

set r [expr {(\$f + \$h) % \$n}]               ;# How much we're off by

if {\$r != 0} {
if {\$r > \$n / 2} {
set r [expr {\$r - \$n}]
}
set ss(flight) [expr {\$ss(flight) - \$r}] ;# Adjust flight down
set ss(v,flight) \$ss(flight)            ;# Set the scale
}
}
##+#####################################################
#
# Toss_ball - Called when a ball has just been tossed. We need to
# update the hand info.
#
proc toss_ball {who which} {
global ball hand ss

set next [next_ball \$who \$which]            ;# Next ball to land here
set hand(\$which,ss)         empty           ;# No longer holding a ball
set hand(\$which,ball)       -1              ;# Ball in hand
set hand(\$which,catch)      \$ball(\$next,catch) ;# Next ball to land here
set hand(\$which,duration)   [expr {\$ball(\$next,catch) - \$ss(t)}]
}
##+#####################################################
#
# Catch_ball - Called when ball WHO lands in hand WHICH. Generates a
# new toss and updates the hand information.
#
proc catch_ball {who which} {
global ball hand ss

set dirs(RL) to_right
set dirs(LR) to_left

set next [next_ball \$who \$which]            ;# Next ball to land here

set when [expr {(\$ss(t) + \$ball(\$next,catch)) /2.0}];# Time for us to leave
set when [expr {round(\$when)}]
if {\$when == \$ss(t)} {                      ;# Problem when WHO == NEXT
set when [expr {\$ss(t) + \$ss(hold)}]
}
if {0 && \$which == 0} {
puts -nonewline "catch \$who: time \$ss(t) catch(\$next) "
puts -nonewline "\$ball(\$next,catch) when \$when "
puts "when: +[expr {\$when - \$ss(t)}]"
}

new_toss \$who \$when \$dirs(\$ball(\$who,ss)) \$which

set hand(\$which,ss)         full            ;# Holding a ball
set hand(\$which,ball)       \$who            ;# Ball in hand
set hand(\$which,toss)       \$ball(\$who,toss);# When we throw it
set hand(\$which,duration)   [expr {\$ball(\$who,toss) - \$ss(t)}]

set u                       [expr {-1 + 2.0/\$ball(\$who,flight)}]
set y                       [expr {\$ball(\$who,kh) * (1 - \$u*\$u)}]
set hand(\$which,y)          \$y
}
##+#####################################################
#
# Next_ball - Returns the next ball after WHO to land in hand WHICH
#
proc next_ball {w h} {
global ss

incr w -1
if {\$ss(pattern) == "even"} {
if {\$w == -1} {
set w [expr {\$ss(n2) - 1}]
} elseif {\$w == \$ss(n2) - 1} {
set w [expr {\$ss(num) - 1}]
}
} elseif {\$w == -1} {
set w [expr {\$ss(num) - 1}]
}
return \$w
}
##+#####################################################
#
# New_toss - Sets up ball WHO for being tossed again at time WHEN
# in direction DIR.
# new height = k * height
# new flight = sqrt(k) * flight
#
proc new_toss {who when dir xhand} {
global ball ss

set k 1                                     ;# Scaling factor
set f \$ss(flight)                           ;# Total flight time
set x 0                                     ;# Overlap into holding time

if {! \$ss(perfect)} {                       ;# Should we add randomness?
set x [expr {int(rand() * \$ss(hold))}]  ;# Use this much of hold time
set f [expr {\$ss(flight) + \$x}]         ;# New flight time
set k [expr {1.0 * \$f / \$ss(flight)}]
set k [expr {\$k * \$k}]
}

if {\$dir == "to_right" && \$ss(pattern) == "shower"} {
set f \$ss(flight2)                      ;# Special low path
set k [expr {1.0 * \$f / \$ss(flight)}]
set k [expr {\$k * \$k}]
}
set ball(\$who,k)      \$k                    ;# Random height scale factor
set ball(\$who,toss)   \$when                 ;# Time of the toss
set ball(\$who,flight) \$f                    ;# New flight time
set ball(\$who,catch)  [expr {\$when + \$f}]   ;# Time of catch
set ball(\$who,kh)     [expr {\$k * \$ss(h)}]  ;# How high this toss goes

set ball(\$who,w)      [expr {2 * \$ss(w)}]
if {\$ss(pattern) == "even"} {
set ball(\$who,w)  [expr {-2*\$ss(shift)}]
}
set ball(\$who,x)      [expr {-(\$ss(w) - \$ss(shift))}]
if {\$xhand == 1} {
set ball(\$who,w)  [expr {-\$ball(\$who,w)}]
set ball(\$who,x)  [expr {-\$ball(\$who,x)}]
}

if {\$ss(pattern) == "shower" && \$ss(back) == 1} {
set ball(\$who,w)  [expr {-\$ball(\$who,w)}]
set ball(\$who,x)  [expr {-\$ball(\$who,x)}]
}
}
##+#####################################################
#
# Dump - Dumps out the ss of a ball or all the balls
#
proc dump {} {
global ball hand ss

puts ""
for {set i 0} {\$i < \$ss(num)} {incr i} {
set msg "Ball \$i: \$ball(\$i,ss)"
set msg "\$msg toss [format %4d \$ball(\$i,toss)]"
set msg "\$msg  catch[format %4d \$ball(\$i,catch)]"
set msg "\$msg  flight[format %4d \$ball(\$i,flight)]"
set msg "\$msg  x  [format %4d \$ball(\$i,x)]"
set msg "\$msg  w  [format %4d \$ball(\$i,w)]"
set msg "\$msg  k  \$ball(\$i,k)"
set msg "\$msg  kh \$ball(\$i,kh)"
puts \$msg
}
for {set i 0} {\$i < 2} {incr i} {
set msg "Hand \$i: [format %5s \$hand(\$i,ss)]"
set msg "\$msg  ball [format %2s \$hand(\$i,ball)]"
set msg "\$msg  toss [format %4d \$hand(\$i,toss)]"
set msg "\$msg  catch [format %4d \$hand(\$i,catch)]"
set msg "\$msg  duration \$hand(\$i,duration)"
set msg "\$msg  y  \$hand(\$i,y)"
puts \$msg
}
puts "time: \$ss(t)"
puts ""
}
##+#####################################################
#
# Init_ball - Given the starting position of a ball, it determines the
# ss the ball is in and what its toss/catch values should be.
#
proc init_ball {who time} {
global ball ss

if {\$time < \$ss(p1)} {                      ;# Left to right
set ball(\$who,ss)       LR
new_toss \$who [expr {-\$time}] to_right 0
} elseif {\$time < \$ss(p2)} {                ;# Hold right
set ball(\$who,ss)       SR
set ball(\$who,ss)       HR
new_toss \$who [expr {\$ss(p2) - \$time}] to_left 1
} elseif {\$time < \$ss(p3)} {                ;# Right to left
set ball(\$who,ss)       RL
new_toss \$who [expr {\$ss(p2) - \$time}] to_left 1
} elseif {\$time < \$ss(p4)} {                ;# Hold left
set ball(\$who,ss)       SL
set ball(\$who,ss)       HL
new_toss \$who [expr {\$ss(p4) - \$time}] to_right 0
} else {
puts "ERROR: init_ball \$who \$time: time out of range"
}
}
##+#####################################################
#
# Startup - Re-init the balls so that they all start in the hands.
# Not fully working yet.
#
proc startup {} {
global ss ball

set newss(LR) SL                            ;# Cheap way to avoid an if
set newss(HR) SR
set newss(RL) SR
set newss(HL) SL

set max \$ss(t)                              ;# Find longest in air
for {set i 0} {\$i < \$ss(num)} {incr i} {
if {\$ball(\$i,toss) < \$max} { set max \$ball(\$i,toss) }
}

set max [expr {\$ss(t) - \$max}]
for {set i 0} {\$i < \$ss(num)} {incr i} {    ;# Adjust everyone by max
set ball(\$i,toss)       [expr {\$ball(\$i,toss) + \$max}]
set ball(\$i,catch)      [expr {\$ball(\$i,toss) + \$ball(\$i,flight)}]
set ball(\$i,ss) \$newss(\$ball(\$i,ss))

move_ball 0 \$i
}
}
##+#####################################################
#
# Init_hands - Initializes where the hands are
#
proc init_hands {} {
global ball ss hand

if {\$ss(pattern) == "shower"} return

set hand(0,y) 20
set hand(1,y) 20

toss_ball 0 0                               ;# Just tossed off ball 0
if {[expr {(\$ss(num) % 2) == 0}]} {
toss_ball [expr {\$ss(num) / 2}] 1
return
}

set hand(0,toss)    0                       ;# When ball gets tossed
set who [expr {\$ss(num) / 2}]               ;# Ball in the right hand
set hand(1,ss) full                         ;# It has a ball in it
set hand(1,ball)  \$who                      ;# Which ball
set hand(1,toss)  \$ball(\$who,toss)          ;# When toss will happen
set hand(1,catch) 0                         ;# When next ball lands
set hand(1,duration) \$ss(hold)              ;# How long we hold ball for
}
##+#####################################################
#
# Cascade - Sets up balls & hands for the cascade pattern
#
global ball ss

best                                        ;# Set up HOLD
set ss(pattern) cascade                     ;# Indicate this pattern
set ss(t) 0                                 ;# Start at time 0
set ss(shift) [expr {(1 - 2*\$ss(back)) * \$ss(s)}]

set ss(p1) \$ss(flight)                      ;# Cycle timings
set ss(p2) [expr {\$ss(p1) + \$ss(hold)}]
set ss(p3) [expr {\$ss(p2) + \$ss(flight)}]
set ss(p4) [expr {\$ss(p3) + \$ss(hold)}]
set ss(total) \$ss(p4)

.c delete balls
for {set i 0} {\$i < \$ss(num)} {incr i} {
create_ball \$i
init_ball \$i [expr {\$ss(total) * \$i / \$ss(num)}]
}
#startup                                    ;# Put into start position

create_hand 0
create_hand 1
init_hands

juggle 0                                    ;# Put them in position
juggle 0                                    ;# Don't ask, it looks better
}
##+#####################################################
#
# Shower - Sets up for the shower pattern
#
proc shower {} {
global ball ss

set ss(pattern) shower                      ;# Indicate this pattern
set ss(t) 0
set ss(shift) 0                            ;# Get rid of the shift
.c delete hands

## total = f + 2hold + f2
## f2 = total/n              ==> f/(n-2)
## hold = 1/2 * (total / n)  ==> f/2(n-2)
set ss(flight2)     [expr {round(\$ss(flight) / (\$ss(num) - 2.0))}]
if {\$ss(flight2) <= 1} { set ss(flight2) 2 }
if {\$ss(flight2) >= 5} { set ss(flight2) 4 }
set ss(hold)        [expr {round(\$ss(flight2) / 2.0)}]

set ss(p1)  \$ss(flight2)                    ;# Cycle timings
set ss(p2)  [expr {\$ss(p1) + \$ss(hold)}]
set ss(p3)  [expr {\$ss(p2) + \$ss(flight)}]
set ss(p4)  [expr {\$ss(p3) + \$ss(hold)}]
set ss(total)       \$ss(p4)

.c delete balls
for {set i 0} {\$i < \$ss(num)} {incr i} {
create_ball \$i
init_ball \$i [expr {\$ss(total) * \$i / \$ss(num)}]
}

juggle 0                                    ;# Put them in position
}
##+#####################################################
#
# Even - Sets up for even ball pattern
#
proc even {} {
global ball hand ss

set ss(pattern) even                        ;# Indicate this pattern
set ss(t) 0
set ss(shift) [expr {(1 - 2*\$ss(back)) * 2*\$ss(s)}] ;# Bigger shift
set ss(w) [expr {round(\$ss(scale) * 110)}]  ;# Width of hands

set ss(HR) LR                               ;# Change the transitions
set ss(HL) RL

set ss(hold) [expr {\$ss(flight) / (\$ss(num) - 1)}]
set ss(total) [expr {\$ss(flight) + \$ss(hold)}]
set n2 [expr {round(\$ss(num) / 2.0)}]       ;# Balls in left hand
set n3 [expr {\$ss(num) - \$n2}]              ;# Balls in right hand
set ss(n2) \$n2

.c delete balls
for {set i 0} {\$i < \$n2} {incr i} {         ;# Left hand
create_ball \$i                          ;# New ball
set t [expr {-\$ss(total) * \$i / \$n2}]   ;# When it got tossed
new_toss \$i \$t xxx 0                    ;# Put in then toss values
set ball(\$i,ss) RL                      ;# Reset the ss info
if [expr {\$t > \$ss(flight)}] {
set ball(\$i2,ss) SL
set ball(\$i2,ss) HL
}
}

set offset [expr {\$n2 == \$n3 ? \$ss(hold) : 0}]
for {set i \$n2} {\$i < \$ss(num)} {incr i} {  ;# Right hand
set i2 [expr {\$i - \$n2}]                ;# Ball in other hand

create_ball \$i
set t [expr {-\$ss(total) * \$i2 / \$n3}]  ;# When it got tossed
set t [expr {-\$offset + \$t}]            ;# Offset it a little
new_toss \$i \$t xxx 1                    ;# Put in the toss values
set ball(\$i,ss) LR                      ;# Reset the ss info
if [expr {\$t > \$ss(flight)}] {
set ball(\$i,ss) SR
set ball(\$i,ss) HR
}

}
create_hand 0
create_hand 1
toss_ball 0 0
toss_ball \$n2 1

juggle 0                                    ;# Put them in position
juggle 0                                    ;# Don't ask, it looks better
}
proc wink {onoff} {
catch {after cancel \$::ss(wink)}
if {\$onoff} {
.c lower reye
.c raise wink flagman
set ::ss(wink) [after 500 {wink 0}]
} else {
.c lower wink
.c raise reye flagman
set delay [expr {int(1000 * (10 + 40*rand()))}]
set ::ss(wink) [after \$delay {wink 1}]
}
}
##+#####################################################
#
# Place_obj
#
# Moves OBJ to absolute coordinates (x,y). If center is 0 then the
# top left corner moves to (x,y). If center is 1 then the object is
# centered at (x,y). If center is -1, then only centered in x.
#
proc place_obj {obj xy {center 1}} {
global ss

foreach {x y} \$xy break
set bb [.c bbox \$obj]                       ;# Where it is
set x [expr {\$x - \$ss(s2)}]                 ;# Center at this point
if {\$center != -1} {
set y [expr {\$y - \$ss(s2)}]
}

set dx [expr {\$x - [lindex \$bb 0]}]         ;# Delta in X
set dy [expr {\$y - [lindex \$bb 1]}]         ;# Delta in Y

.c move \$obj \$dx \$dy                        ;# Move into place
}
tk_messageBox -icon info -parent . -title "About TkJuggler" \
-message "Tk Juggler\n\nby Keith Vetter\nNovember, 2002"
}
proc flagman {} {
# stolen from https://wiki.tcl-lang.org/3208
.c create rect {-5000 110 5000 5000} -fill grey -outline grey -tag flagman
.c create poly {-80 280 -20 280 0 80 20 280 80 280 100 -136 0 \
-160 -100 -136} -fill white -tag flagman
.c create oval {-40 -236 40 -140} -fill orange  -outline orange -tag flagman
.c create line {-16 -200 -16 -188} -tag {flagman reye}
.c create line {-8 -194 -24 -194} -tag {flagman wink}
.c create line {16  -200  16 -188} -tag flagman
.c create arc -24 -216 24 -160 -start 210 -extent 125 -style arc \
-tag flagman
.c create rect {-36 -236 36 -216} -fill white -outline white -tag flagman

.c create poly {-80 -120 -100 -120 -100 0 -60 0 -60 -120} -fill grey95 \
-tag flagman
.c create poly {80 -120 100 -120  100 0  60 0 60 -120} -fill grey95 \
-tag flagman
.c lower wink

array set ::ss {elbowx,0 -80 elbowy,0 -10 elbowx,1 80 elbowy,1 -10}
}
proc scaler {bigger} {
global ss

if {\$bigger} {
if {\$ss(scale) > 2} return
set f 1.25
} else {
if {\$ss(scale) < .15} return
set f .8
}
.c scale all 0 0 \$f \$f

foreach w {scale s s2 w v,h elbowx,0 elbowx,1} {
set ss(\$w) [expr {\$ss(\$w) * \$f}]
}
set ss(v,flight) [expr {round(\$ss(v,flight) / \$f)}]
reinit
}
##+##############################################################
#################################################################
#################################################################
init                                            ;# One time inits
display                                         ;# Set up all the widgets
reinit                                          ;# Inits for this pattern
startstop```

uniquename 2013jul29

This code could use an image to show what it produces. (It seems the images above, at mini.net and juggling.org, have gone dead.)

(Thanks to 'gnome-screenshot', 'mtpaint', and ImageMagick 'convert' on Linux for, respectively, capturing a screen image to a PNG file, cropping the image, and converting the PNG file to a JPEG file that was less than 10% the size of the PNG file. Thanks to FOSS developers everywhere --- including Linux kernel and Gnu developers. I used the 'mv' command and the ImageMagick 'identify' command in a shell script to easily rename the cropped image file to contain the image dimensions in pixels.)

I captured the image above while the juggler was juggling. Hence the 'break up' of the balls into filled partial-circles.

Note the controls along the bottom of the GUI, to set up different juggling patterns, speeds, and heights.

 Category Graphics Category Application