1#
2# Ruby/Tk Goldverg demo (called by 'widget')
3#
4# Based on Tcl/Tk8.5a2 widget demos.
5# The following is the original comment of TkGoldberg.tcl.
6#
7#>>##+#################################################################
8#>>#
9#>># TkGoldberg.tcl
10#>># by Keith Vetter, March 13, 2003
11#>>#
12#>># "Man will always find a difficult means to perform a simple task"
13#>># Rube Goldberg
14#>>#
15#>># Reproduced here with permission.
16#>>#
17#>>##+#################################################################
18#>>#
19#>># Keith Vetter 2003-03-21: this started out as a simple little program
20#>># but was so much fun that it grew and grew. So I apologize about the
21#>># size but I just couldn't resist sharing it.
22#>>#
23#>># This is a whizzlet that does a Rube Goldberg type animation, the
24#>># design of which comes from an New Years e-card from IncrediMail.
25#>># That version had nice sound effects which I eschewed. On the other
26#>># hand, that version was in black and white (actually dark blue and
27#>># light blue) and this one is fully colorized.
28#>>#
29#>># One thing I learned from this project is that drawing filled complex
30#>># objects on a canvas is really hard. More often than not I had to
31#>># draw each item twice--once with the desired fill color but no
32#>># outline, and once with no fill but with the outline. Another trick
33#>># is erasing by drawing with the background color. Having a flood fill
34#>># command would have been extremely helpful.
35#>>#
36#>># Two wiki pages were extremely helpful: Drawing rounded rectangles
37#>># which I generalized into Drawing rounded polygons, and regular
38#>># polygons which allowed me to convert ovals and arcs into polygons
39#>># which could then be rotated (see Canvas Rotation). I also wrote
40#>># Named Colors to aid in the color selection.
41#>>#
42#>># I could comment on the code, but it's just 26 state machines with
43#>># lots of canvas create and move calls.
44
45if defined?($goldberg_demo) && $goldberg_demo
46  $goldberg_demo.destroy
47  $goldberg_demo = nil
48end
49
50# demo toplevel widget
51$goldberg_demo = TkToplevel.new {|w|
52  title("Tk Goldberg (demonstration)")
53  iconname("goldberg")
54#  positionWindow(w)
55}
56
57base_frame = TkFrame.new($goldberg_demo).pack(:fill=>:both, :expand=>true)
58
59=begin
60# label
61msg = TkLabel.new($goldberg_demo) {
62  font 'Arial 10'
63  wraplength '4i'
64  justify 'left'
65  text "This is a demonstration of just how complex you can make your animations become. Click the ball to start things moving!\n\n\"Man will always find a difficult means to perform a simple task\"\n - Rube Goldberg"
66}
67msg.pack('side'=>'top')
68=end
69
70=begin
71# frame
72TkFrame.new($goldberg_demo) {|frame|
73  TkButton.new(frame) {
74    text 'Dismiss'
75    command proc{
76      tmppath = $goldberg_demo
77      $goldberg_demo = nil
78      tmppath.destroy
79    }
80  }.pack('side'=>'left', 'expand'=>'yes')
81
82  TkButton.new(frame) {
83    text 'See Code'
84    command proc{showCode 'goldberg'}
85  }.pack('side'=>'left', 'expand'=>'yes')
86
87}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m')
88=end
89
90#########################################
91
92class TkGoldberg_Demo
93  def initialize(parent)
94    @parent = parent
95
96    @S = {}
97    @S['title']   = 'Tk Goldberg'
98    @S['speed']   = TkVariable.new(5)
99    @S['cnt']     = TkVariable.new(0)
100    @S['message'] = TkVariable.new("\\nWelcome\\nto\\nRuby/Tk")
101    @S['pause']   = TkVariable.new
102    @S['details'] = TkVariable.new(true)
103
104    @S['mode'] = TkVariable.new(:MSTART, :symbol)
105    #            :MSTART, :MGO, :MPAUSE, :MSSTEP, :MBSTEP, :MDONE, :MDEBUG
106
107    #         0,  1,  2,  3,  4,  5,   6,   7,   8,   9,  10
108    @speed = [1, 10, 20, 50, 80, 100, 150, 200, 300, 400, 500]
109
110    # colors
111    @C = {}
112    @C['fg'] = 'black'
113    # @C['bg'] = 'gray75'
114    @C['bg'] = 'cornflowerblue'
115
116    @C['0'] = 'white';         @C['1a'] = 'darkgreen';   @C['1b'] = 'yellow'
117    @C['2'] = 'red';           @C['3a'] = 'green';       @C['3b'] = 'darkblue'
118    @C['4'] = @C['fg'];        @C['5a'] = 'brown';       @C['5b'] = 'white'
119    @C['6'] = 'magenta';       @C['7'] = 'green';        @C['8'] = @C['fg']
120    @C['9'] = 'blue4';         @C['10a'] = 'white';      @C['10b'] = 'cyan'
121    @C['11a'] = 'yellow';      @C['11b'] = 'mediumblue'; @C['12'] = 'tan2'
122    @C['13a'] = 'yellow';      @C['13b'] = 'red';        @C['14'] = 'white'
123    @C['15a'] = 'green';       @C['15b'] = 'yellow';     @C['16'] = 'gray65'
124    @C['17'] = '#A65353';     @C['18'] = @C['fg'];      @C['19'] = 'gray50'
125    @C['20'] = 'cyan';         @C['21'] = 'gray65';      @C['22'] = @C['20']
126    @C['23a'] = 'blue';        @C['23b'] = 'red';        @C['23c'] = 'yellow'
127    @C['24a'] = 'red';         @C['24b'] = 'white';
128
129    @STEP = TkVariable.new_hash
130    @STEP.default_value_type = :numeric
131
132    @XY = {}
133
134    @XY6 = {
135      '-1'=>[366, 207], '-2'=>[349, 204], '-3'=>[359, 193], '-4'=>[375, 192],
136      '-5'=>[340, 190], '-6'=>[349, 177], '-7'=>[366, 177], '-8'=>[380, 176],
137      '-9'=>[332, 172], '-10'=>[342, 161], '-11'=>[357, 164],
138      '-12'=>[372, 163], '-13'=>[381, 149], '-14'=>[364, 151],
139      '-15'=>[349, 146], '-16'=>[333, 148], '0'=>[357, 219],
140      '1'=>[359, 261], '2'=>[359, 291], '3'=>[359, 318], '4'=>[361, 324],
141      '5'=>[365, 329], '6'=>[367, 334], '7'=>[367, 340], '8'=>[366, 346],
142      '9'=>[364, 350], '10'=>[361, 355], '11'=>[359, 370], '12'=>[359, 391],
143      '13,0'=>[360, 456], '13,1'=>[376, 456], '13,2'=>[346, 456],
144      '13,3'=>[330, 456], '13,4'=>[353, 444], '13,5'=>[368, 443],
145      '13,6'=>[339, 442], '13,7'=>[359, 431], '13,8'=>[380, 437],
146      '13,9'=>[345, 428], '13,10'=>[328, 434], '13,11'=>[373, 424],
147      '13,12'=>[331, 420], '13,13'=>[360, 417], '13,14'=>[345, 412],
148      '13,15'=>[376, 410], '13,16'=>[360, 403]
149    }
150
151    @timer = TkTimer.new(@speed[@S['speed'].numeric]){|timer|
152      timer.set_interval(go)
153    }
154
155    do_display
156    reset
157
158    # Start everything going
159    @timer.start
160  end
161
162  def do_display()
163    @ctrl = TkFrame.new(@parent, :relief=>:ridge, :bd=>2, :padx=>5, :pady=>5)
164    @screen = TkFrame.new(@parent, :bd=>2,
165                         :relief=>:raised).pack(:side=>:left, :fill=>:both,
166                                                :expand=>true)
167
168    @canvas = TkCanvas.new(@parent, :width=>850, :height=>700,
169                          :bg=>@C['bg'], :highlightthickness=>0){
170      scrollregion([0, 0, 1000, 1000]) # Kludge to move everything up
171      yview_moveto(0.05)
172    }.pack(:in=>@screen, :side=>:top, :fill=>:both, :expand=>true)
173
174    @canvas.bind('3'){ @pause.invoke }
175    @canvas.bind('Destroy'){ @timer.stop }
176
177    do_ctrl_frame
178    do_detail_frame
179
180    # msg = TkLabel.new(@parent, :bg=>@C['bg'], :fg=>'white') {
181    msg = Tk::Label.new(@parent, :bg=>@C['bg'], :fg=>'white') {
182      font 'Arial 10'
183      wraplength 600
184      justify 'left'
185      text "This is a demonstration of just how complex you can make your animations become. Click the ball to start things moving!\n\"Man will always find a difficult means to perform a simple task\" - Rube Goldberg"
186    }
187    msg.place(:in=>@canvas, :relx=>0, :rely=>0, :anchor=>:nw)
188
189    frame = TkFrame.new(@parent, :bg=>@C['bg'])
190
191    # TkButton.new(frame, :bg=>@C['bg'], :activebackground=>@C['bg']) {
192    Tk::Button.new(frame, :bg=>@C['bg'], :activebackground=>@C['bg']) {
193      text 'Dismiss'
194      command proc{
195        tmppath = $goldberg_demo
196        $goldberg_demo = nil
197        tmppath.destroy
198      }
199    }.pack('side'=>'left')
200
201    # TkButton.new(frame, :bg=>@C['bg'], :activebackground=>@C['bg']) {
202    Tk::Button.new(frame, :bg=>@C['bg'], :activebackground=>@C['bg']) {
203      text 'See Code'
204      command proc{showCode 'goldberg'}
205    }.pack('side'=>'left', 'padx'=>5)
206
207    # @show = TkButton.new(frame, :text=>'>>', :command=>proc{show_ctrl},
208    @show = Tk::Button.new(frame, :text=>'>>', :command=>proc{show_ctrl},
209                         :bg=>@C['bg'], :activebackground=>@C['bg'])
210    @show.pack('side'=>'left')
211    frame.place(:in=>@canvas, :relx=>1, :rely=>0, :anchor=>:ne)
212
213    Tk.update
214  end
215
216  def do_ctrl_frame
217    @start = Tk::Button.new(@parent, :text=>'Start', :bd=>6,
218                          :command=>proc{do_button(0)})
219    if font = @start['font']
220      @start.font(font.weight('bold'))
221    end
222
223    @pause = TkCheckbutton.new(@parent, :text=>'Pause', :font=>font,
224                               :command=>proc{do_button(1)}, :relief=>:raised,
225                               :variable=>@S['pause'])
226
227    @step  = TkButton.new(@parent, :text=>'Single Step', :font=>font,
228                          :command=>proc{do_button(2)})
229    @bstep = TkButton.new(@parent, :text=>'Big Step', :font=>font,
230                          :command=>proc{do_button(4)})
231    @reset = TkButton.new(@parent, :text=>'Reset', :font=>font,
232                          :command=>proc{do_button(3)})
233
234    @details = TkFrame.new(@parent, :bd=>2, :relief=>:ridge)
235    @detail = TkCheckbutton.new(@parent, :text=>'Details', :font=>font,
236                               :relief=>:raised, :variable=>@S['details'])
237
238    @msg_entry = TkEntry.new(@parent, :textvariable=>@S['message'],
239                             :justify=>:center)
240    @speed_scale = TkScale.new(@parent, :orient=>:horizontal,
241                               :from=>1, :to=>10, :font=>font,
242                               :variable=>@S['speed'], :bd=>2,
243                               :relief=>:ridge, :showvalue=>false)
244    @about = TkButton.new(@parent, :text=>'About',
245                          :command=>proc{about}, :font=>font)
246
247    Tk.grid(@start, :in=>@ctrl, :row=>0, :sticky=>:ew)
248    @ctrl.grid_rowconfigure(1, :minsize=>10)
249    Tk.grid(@pause, :in=>@ctrl, :row=>2, :sticky=>:ew)
250    Tk.grid(@step,  :in=>@ctrl, :sticky=>:ew)
251    Tk.grid(@bstep, :in=>@ctrl, :sticky=>:ew)
252    Tk.grid(@reset, :in=>@ctrl, :sticky=>:ew)
253    @ctrl.grid_rowconfigure(10, :minsize=>20)
254    Tk.grid(@details, :in=>@ctrl, :row=>11, :sticky=>:ew)
255    Tk.grid(@detail, :in=>@details, :row=>0, :sticky=>:ew)
256    @ctrl.grid_rowconfigure(50, :weight=>1)
257
258    @S['mode'].trace('w', proc{|*args| active_GUI(*args)})
259    @S['details'].trace('w', proc{|*args| active_GUI(*args)})
260    @S['speed'].trace('w', proc{|*args| active_GUI(*args)})
261
262    Tk.grid(@msg_entry, :in=>@ctrl, :row=>98, :sticky=>:ew, :pady=>5)
263    Tk.grid(@speed_scale, :in=>@ctrl, :row=>99, :sticky=>:ew)
264    Tk.grid(@about, :in=>@ctrl, :row=>100, :sticky=>:ew)
265
266    @reset.bind('3'){@S['mode'].value = -1}  # Debugging
267  end
268
269  def do_detail_frame
270    @f_details = TkFrame.new(@details)
271
272    @label = TkLabel.new(@f_details, :textvariable=>@S['cnt'],
273                         :bd=>1, :relief=>:solid, :bg=>'white')
274    Tk.grid(@label, '-', '-', '-', :sticky=>:ew, :row=>0)
275
276    idx = 1
277    loop {
278      break unless respond_to?("move#{idx}")
279      l = TkLabel.new(@f_details, :text=>idx, :anchor=>:e,
280                      :width=>2, :bd=>1, :relief=>:solid, :bg=>'white')
281      @STEP[idx] = 0
282      ll = TkLabel.new(@f_details, :textvariable=>@STEP.ref(idx),
283                       :width=>5, :bd=>1, :relief=>:solid, :bg=>'white')
284      row = (idx + 1)/2
285      col = ((idx + 1) & 1) * 2
286      Tk.grid(l, :sticky=>:ew, :row=>row, :column=>col)
287      Tk.grid(ll, :sticky=>:ew, :row=>row, :column=>(col + 1))
288      idx += 1
289    }
290    @f_details.grid_columnconfigure(1, :weight=>1)
291  end
292
293  def show_ctrl
294    if @ctrl.winfo_mapped?
295      @ctrl.pack_forget
296      @show.text('>>')
297    else
298      @ctrl.pack(:side=>:right, :fill=>:both, :ipady=>5)
299      @show.text('<<')
300    end
301  end
302
303  def draw_all
304    reset_step
305    @canvas.delete(:all)
306    idx = 0
307    loop{
308      m = "draw#{idx}"
309      break unless respond_to?(m)
310      send(m)
311      idx += 1
312    }
313  end
314
315  def active_GUI(var1, var2, op)
316    st = {false=>:disabled, true=>:normal}
317
318    m = @S['mode'].to_sym
319    @S['pause'].value = (m == :MPAUSE)
320    @start.state(st[m != :MGO])
321    @pause.state(st[m != :MSTART && m != :MDONE])
322    @step.state(st[m != :MGO && m != :MDONE])
323    @bstep.state(st[m != :MGO && m != :MDONE])
324    @reset.state(st[m != :MSTART])
325
326    if @S['details'].bool
327      Tk.grid(@f_details, :in=>@details, :row=>2, :sticky=>:ew)
328    else
329      Tk.grid_forget(@f_details)
330    end
331    @speed_scale.label("Speed: #{@S['speed'].value}")
332  end
333
334  def start
335    @S['mode'].value = :MGO
336  end
337
338  def do_button(what)
339    case what
340    when 0  # Start
341      reset if @S['mode'].to_sym == :MDONE
342      @S['mode'].value = :MGO
343
344    when 1  # Pause
345      @S['mode'].value = ((@S['pause'].bool)? :MPAUSE: :MGO)
346
347    when 2  # Step
348      @S['mode'].value = :MSSTEP
349
350    when 3  # Reset
351      reset
352
353    when 4  # Big step
354      @S['mode'].value = :MBSTEP
355    end
356  end
357
358  def go(who = nil)
359    now = Tk::Clock.clicks(:miliseconds)
360    if who  # Start here for debugging
361      @S['active'] = [who]
362      @S['mode'].value = :MGO
363    end
364    return if @S['mode'].to_sym == :MDEBUG  # Debugging
365    # If not paused, do the next move
366    n = next_step if @S['mode'].to_sym != :MPAUSE
367    @S['mode'].value = :MPAUSE if @S['mode'].to_sym == :MSSTEP  # Single step
368    @S['mode'].value = :MSSTEP if @S['mode'].to_sym == :MBSTEP && n  # big step
369    elapsed = Tk::Clock.clicks(:miliseconds) - now
370    delay = @speed[@S['speed'].to_i] - elapsed
371    delay = 1 if delay <= 0
372    return delay
373  end
374
375  def next_step
376    retval = false   # Return value
377
378    if @S['mode'].to_sym != :MSTART && @S['mode'].to_sym != :MDONE
379      @S['cnt'].numeric += 1
380    end
381    alive = []
382    @S['active'].each{|who|
383      who = who.to_i
384      n = send("move#{who}")
385      if (n & 1).nonzero?          # This guy still alive
386        alive << who
387      end
388      if (n & 2).nonzero?          # Next guy is active
389        alive << (who + 1)
390        retval = true
391      end
392      if (n & 4).nonzero?          # End of puzzle flag
393        @S['mode'].value = :MDONE  # Done mode
394        @S['active'] = []          # No more animation
395        return true
396      end
397    }
398    @S['active'] = alive
399    return retval
400  end
401
402  def about
403    msg = "Ruby/Tk Version ::\nby Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)\n\n"
404    msg += "Original Version ::\n"
405    msg += "#{@S['title']}\nby Keith Vetter, March 2003\n(Reproduced by kind permission of the author)\n\n"
406    msg += "Man will always find a difficult means to perform a simple task"
407    msg += "\nRube Goldberg"
408    Tk.messageBox(:message=>msg, :title=>'About')
409  end
410
411  ################################################################
412  #
413  # All the drawing and moving routines
414  #
415
416  # START HERE! banner
417  def draw0
418    color = @C['0']
419    TkcText.new(@canvas, [579, 119], :text=>'START HERE!',
420                :fill=>color, :anchor=>:w,
421                :tag=>'I0', :font=>['Times Roman', 12, :italic, :bold])
422    TkcLine.new(@canvas, [719, 119, 763, 119], :tag=>'I0', :fill=>color,
423                :width=>5, :arrow=>:last, :arrowshape=>[18, 18, 5])
424    @canvas.itembind('I0', '1'){ start }
425  end
426
427  def move0(step = nil)
428    step = get_step(0, step)
429
430    if @S['mode'].to_sym != :MSTART    # Start the ball rolling
431      move_abs('I0', [-100, -100])     # Hide the banner
432      return 2
433    end
434
435    pos = [
436      [673, 119], [678, 119], [683, 119], [688, 119],
437      [693, 119], [688, 119], [683, 119], [678, 119]
438    ]
439    step = step % pos.length
440    move_abs('I0', pos[step])
441    return 1
442  end
443
444  # Dropping ball
445  def draw1
446    color = @C['1a']
447    color2 = @C['1b']
448    TkcPolygon.new(@canvas,
449                   [ 844, 133, 800, 133, 800, 346, 820, 346,
450                     820, 168, 844, 168, 844, 133 ],
451                   :width=>3, :fill=>color, :outline=>'')
452    TkcPolygon.new(@canvas,
453                   [ 771, 133, 685, 133, 685, 168, 751, 168,
454                     751, 346, 771, 346, 771, 133 ],
455                   :width=>3, :fill=>color, :outline=>'')
456    TkcOval.new(@canvas, box(812, 122, 9),
457                :tag=>'I1', :fill=>color2, :outline=>'')
458
459    @canvas.itembind('I1', '1'){ start }
460  end
461
462  def move1(step = nil)
463    step = get_step(1, step)
464    pos = [
465      [807, 122], [802, 122], [797, 123], [793, 124], [789, 129], [785, 153],
466      [785, 203], [785, 278, :x], [785, 367], [810, 392], [816, 438],
467      [821, 503], [824, 585, :y], [838, 587], [848, 593], [857, 601],
468      [-100, -100]
469    ]
470    return 0 if step >= pos.length
471    where = pos[step]
472    move_abs('I1', where)
473    move15a if where[2] == :y
474    return 3 if where[2] == :x
475    return 1
476  end
477
478  # Lighting the match
479  def draw2
480    color = @C['2']
481
482    # Fulcrum
483    TkcPolygon.new(@canvas, [750, 369, 740, 392, 760, 392],
484                   :fill=>@C['fg'], :outline=>@C['fg'])
485
486    # Strike box
487    TkcRectangle.new(@canvas, [628, 335, 660, 383],
488                     :fill=>'', :outline=>@C['fg'])
489    (0..2).each{|y|
490      yy = 335 + y*16
491      TkcBitmap.new(@canvas, [628, yy], :bitmap=>'gray25',
492                    :anchor=>:nw, :foreground=>@C['fg'])
493      TkcBitmap.new(@canvas, [644, yy], :bitmap=>'gray25',
494                    :anchor=>:nw, :foreground=>@C['fg'])
495    }
496
497    # Lever
498    TkcLine.new(@canvas, [702, 366, 798, 366],
499                :fill=>@C['fg'], :width=>6, :tag=>'I2_0')
500
501    # R strap
502    TkcLine.new(@canvas, [712, 363, 712, 355],
503                :fill=>@C['fg'], :width=>3, :tag=>'I2_1')
504
505    # L strap
506    TkcLine.new(@canvas, [705, 363, 705, 355],
507                :fill=>@C['fg'], :width=>3, :tag=>'I2_2')
508
509    # Match stick
510    TkcLine.new(@canvas, [679, 356, 679, 360, 717, 360, 717, 356, 679, 356],
511                :fill=>@C['fg'], :width=>3, :tag=>'I2_3')
512
513    # Match head
514    TkcPolygon.new(@canvas,
515                   [ 671, 352, 677.4, 353.9, 680, 358.5, 677.4, 363.1,
516                     671, 365, 664.6, 363.1, 662, 358.5, 664.6, 353.9 ],
517                   :fill=>color, :outline=>color, :tag=>'I2_4')
518  end
519
520  def move2(step = nil)
521    step = get_step(2, step)
522
523    stages = [0, 0, 1, 2, 0, 2, 1, 0, 1, 2, 0, 2, 1]
524    xy = []
525    xy[0] = [
526      686, 333, 692, 323, 682, 316, 674, 309, 671, 295, 668, 307,
527      662, 318, 662, 328, 671, 336
528    ]
529    xy[1] = [
530      687, 331, 698, 322, 703, 295, 680, 320, 668, 297, 663, 311,
531      661, 327, 671, 335
532    ]
533    xy[2] = [
534      686, 331, 704, 322, 688, 300, 678, 283, 678, 283, 674, 298,
535      666, 309, 660, 324, 672, 336
536    ]
537
538    if step >= stages.length
539      @canvas.delete('I2')
540      return 0
541    end
542
543    if step == 0  # Rotate the match
544      beta = 20
545
546      ox, oy = anchor('I2_0', :s)  # Where to pivot
547
548      i = 0
549      until @canvas.find_withtag("I2_#{i}").empty?
550        rotate_item("I2_#{i}", ox, oy, beta)
551        i += 1
552      end
553
554      # For the flame
555      TkcPolygon.new(@canvas, [], :tag=>'I2', :smooth=>true, :fill=>@C['2'])
556
557      return 1
558    end
559    @canvas.coords('I2', xy[stages[step]])
560    return ((step == 7)? 3: 1)
561  end
562
563  # Weight and pulleys
564  def draw3
565    color = @C['3a']
566    color2 = @C['3b']
567
568    xy = [ [602, 296], [577, 174], [518, 174] ]
569    xy.each{|x, y| # 3 Pulleys
570      TkcOval.new(@canvas, box(x, y, 13),
571                  :fill=>color, :outline=>@C['fg'], :width=>3)
572      TkcOval.new(@canvas, box(x, y, 2), :fill=>@C['fg'], :outline=>@C['fg'])
573    }
574
575    # Wall to flame
576    TkcLine.new(@canvas, [750, 309, 670, 309], :tag=>'I3_s',
577                :width=>3, :fill=>@C['fg'], :smooth=>true)
578
579    # Flame to pulley 1
580    TkcLine.new(@canvas, [670, 309, 650, 309], :tag=>'I3_0',
581                :width=>3, :fill=>@C['fg'], :smooth=>true)
582    TkcLine.new(@canvas, [650, 309, 600, 309], :tag=>'I3_1',
583                :width=>3, :fill=>@C['fg'], :smooth=>true)
584
585    # Pulley 1 half way to 2
586    TkcLine.new(@canvas, [589, 296, 589, 235], :tag=>'I3_2',
587                :width=>3, :fill=>@C['fg'])
588
589    # Pulley 1 other half to 2
590    TkcLine.new(@canvas, [589, 235, 589, 174], :width=>3, :fill=>@C['fg'])
591
592    # Across the top
593    TkcLine.new(@canvas, [577, 161, 518, 161], :width=>3, :fill=>@C['fg'])
594
595    # Down to weight
596    TkcLine.new(@canvas, [505, 174, 505, 205], :tag=>'I3_w',
597                :width=>3, :fill=>@C['fg'])
598
599    # Draw the weight as 2 circles, two rectangles and 1 rounded rectangle
600    x1, y1, x2, y2 = [515, 207, 495, 207]
601    TkcOval.new(@canvas, box(x1, y1, 6),
602                :tag=>'I3_', :fill=>color2, :outline=>color2)
603    TkcOval.new(@canvas, box(x2, y2, 6),
604                :tag=>'I3_', :fill=>color2, :outline=>color2)
605    TkcRectangle.new(@canvas, x1, y1 - 6, x2, y2 + 6,
606                     :tag=>'I3_', :fill=>color2, :outline=>color2)
607
608    TkcPolygon.new(@canvas, round_rect([492, 220, 518, 263], 15),
609                   :smooth=>true, :tag=>'I3_', :fill=>color2, :outline=>color2)
610
611    TkcLine.new(@canvas, [500, 217, 511, 217],
612                :tag=>'I3_', :fill=>color2, :width=>10)
613
614    # Bottom weight target
615    TkcLine.new(@canvas, [502, 393, 522, 393, 522, 465],
616                :tag=>'I3__', :fill=>@C['fg'], :joinstyle=>:miter, :width=>10)
617  end
618
619  def move3(step = nil)
620    step = get_step(3, step)
621
622    pos = [ [505, 247], [505, 297], [505, 386.5], [505, 386.5] ]
623    rope = []
624    rope[0] = [750, 309, 729, 301, 711, 324, 690, 300]
625    rope[1] = [750, 309, 737, 292, 736, 335, 717, 315, 712, 320]
626    rope[2] = [750, 309, 737, 309, 740, 343, 736, 351, 725, 340]
627    rope[3] = [750, 309, 738, 321, 746, 345, 742, 356]
628
629    return 0 if step >= pos.length
630
631    @canvas.delete("I3_#{step}")        # Delete part of the rope
632    move_abs('I3_', pos[step])          # Move weight down
633    @canvas.coords('I3_s', rope[step])  # Flapping rope end
634    @canvas.coords('I3_w', [505, 174].concat(pos[step]))
635    if step == 2
636      @canvas.move('I3__', 0, 30)
637      return 2
638    end
639    return 1
640  end
641
642  # Cage and door
643  def draw4
644    color = @C['4']
645    x0, y0, x1, y1 = [527, 356, 611, 464]
646
647    # Horizontal bars
648    y0.step(y1, 12){|y|
649      TkcLine.new(@canvas, [x0, y, x1, y], :fill=>color, :width=>1)
650    }
651
652    # Vertical bars
653    x0.step(x1, 12){|x|
654      TkcLine.new(@canvas, [x, y0, x, y1], :fill=>color, :width=>1)
655    }
656
657    # Swing gate
658    TkcLine.new(@canvas, [518, 464, 518, 428],
659                :tag=>'I4', :fill=>color, :width=>1)
660  end
661
662  def move4(step = nil)
663    step = get_step(4, step)
664
665    angles = [-10, -20, -30, -30]
666    return 0 if step >= angles.length
667
668    rotate_item('I4', 518, 464, angles[step])
669    @canvas.raise('I4')
670
671    return((step == 3)? 3: 1)
672  end
673
674  # Mouse
675  def draw5
676    color  = @C['5a']
677    color2 = @C['5b']
678
679    xy = [377, 248, 410, 248, 410, 465, 518, 465]  # Mouse course
680    xy.concat [518, 428, 451, 428, 451, 212, 377, 212]
681
682    TkcPolygon.new(@canvas, xy, :fill=>color2, :outline=>@C['fg'], :width=>3)
683
684    xy = [
685      534.5, 445.5, 541, 440, 552, 436, 560, 436, 569, 440, 574, 446,
686      575, 452, 574, 454, 566, 456, 554, 456, 545, 456, 537, 454, 530, 452
687    ]
688    TkcPolygon.new(@canvas, xy, :tag=>['I5', 'I5_0'], :fill=>color)
689
690    TkcLine.new(@canvas, [573, 452, 592, 458, 601, 460, 613, 456], # Tail
691                :tag=>['I5', 'I5_1'], :fill=>color, :smooth=>true, :width=>3)
692
693    xy = box(540, 446, 2)   # Eye
694    xy = [540, 444, 541, 445, 541, 447, 540, 448, 538, 447, 538, 445]
695    TkcPolygon.new(@canvas, xy, :tag=>['I5', 'I5_2'], :fill=>@C['bg'],
696                   :outline=>'', :smooth=>true)
697
698    xy = [538, 454, 535, 461] # Front leg
699    TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_3'], :fill=>color, :width=>2)
700
701    xy = [566, 455, 569, 462] # Back leg
702    TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_4'], :fill=>color, :width=>2)
703
704    xy = [544, 455, 545, 460] # 2nd front leg
705    TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_5'], :fill=>color, :width=>2)
706
707    xy = [560, 455, 558, 460] # 2nd back leg
708    TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_6'], :fill=>color, :width=>2)
709  end
710
711  def move5(step = nil)
712    step = get_step(5, step)
713
714    pos = [
715      [553, 452], [533, 452], [513, 452], [493, 452], [473, 452],
716      [463, 442, 30], [445.5, 441.5, 30], [425.5, 434.5, 30], [422, 414],
717      [422, 394], [422, 374], [422, 354], [422, 334], [422, 314], [422, 294],
718      [422, 274, -30], [422, 260.5, -30, :x], [422.5, 248.5, -28], [425, 237]
719    ]
720
721    return 0 if step >= pos.length
722
723    x, y, beta, nxt = pos[step]
724    move_abs('I5', [x, y])
725    if beta
726      ox, oy = centroid('I5_0')
727      (0..6).each{|id| rotate_item("I5_#{id}", ox, oy, beta) }
728    end
729    return 3 if nxt == :x
730    return 1
731  end
732
733  # Dropping gumballs
734  def draw6
735    color = @C['6']
736    xy = [324, 130, 391, 204] # Ball holder
737    xy = round_rect(xy, 10)
738    TkcPolygon.new(@canvas, xy, :smooth=>true,
739                   :outline=>@C['fg'], :width=>3, :fill=>color)
740    xy = [339, 204, 376, 253] # Below the ball holder
741    TkcRectangle.new(@canvas, xy, :outline=>@C['fg'], :width=>3,
742                     :fill=>color, :tag=>'I6c')
743    xy = box(346, 339, 28)
744    TkcOval.new(@canvas, xy, :fill=>color, :outline=>'') # Roter
745    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>2, :style=>:arc,
746               :start=>80, :extent=>205)
747    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>2, :style=>:arc,
748               :start=>-41, :extent=>85)
749
750    xy = box(346, 339, 15) # Center of rotor
751    TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['fg'], :tag=>'I6m')
752    xy = [352, 312, 352, 254, 368, 254, 368, 322] # Top drop to rotor
753    TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'')
754    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2)
755
756    xy = [353, 240, 367, 300] # Poke bottom hole
757    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'')
758    xy = [341, 190, 375, 210] # Poke another hole
759    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'')
760
761    xy = [
762      368, 356, 368, 403, 389, 403, 389, 464, 320, 464, 320, 403,
763      352, 403, 352, 366
764    ]
765    TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'',
766                   :width=>2) # Below rotor
767    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2)
768    xy = box(275, 342, 7) # On/off rotor
769    TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['fg'])
770    xy = [276, 334, 342, 325] # Fan belt top
771    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
772    xy = [276, 349, 342, 353] # Fan belt bottom
773    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
774
775    xy = [337, 212, 337, 247] # What the mouse pushes
776    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I6_')
777    xy = [392, 212, 392, 247]
778    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I6_')
779    xy = [337, 230, 392, 230]
780    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>7, :tag=>'I6_')
781
782    who = -1 # All the balls
783    colors = %w(red cyan orange green blue darkblue)
784    colors *= 3
785
786    (0..16).each{|i|
787      loc = -i
788      color = colors[i]
789      x, y = @XY6["#{loc}"]
790      TkcOval.new(@canvas, box(x, y, 5),
791                  :fill=>color, :outline=>color, :tag=>"I6_b#{i}")
792    }
793    draw6a(12) # The wheel
794  end
795
796  def draw6a(beta)
797    @canvas.delete('I6_0')
798    ox, oy = [346, 339]
799    (0..3).each{|i|
800      b = beta + i * 45
801      x, y = rotate_c(28, 0, 0, 0, b)
802      xy = [ox + x, oy + y, ox - x, oy - y]
803      TkcLine.new(@canvas, xy, :tag=>'I6_0', :fill=>@C['fg'], :width=>2)
804    }
805  end
806
807  def move6(step = nil)
808    step = get_step(6, step)
809
810    return 0 if step > 62
811
812    if step < 2  # Open gate for balls to drop
813      @canvas.move('I6_', -7, 0)
814      if step == 1  # Poke a hole
815        xy = [348, 226, 365, 240]
816        TkcRectangle.new(@canvas, xy, :fill=>@canvas.itemcget('I6c', :fill),
817                         :outline=>'')
818      end
819      return 1
820    end
821
822    s = step - 1  # Do the gumball drop dance
823    (0..(((s - 1)/3).to_i)).each{|i|
824      tag = "I6_b#{i}"
825      break if @canvas.find_withtag(tag).empty?
826      loc = s - 3*i
827
828      if @XY6["#{loc},#{i}"]
829        move_abs(tag, @XY6["#{loc},#{i}"])
830      elsif @XY6["#{loc}"]
831        move_abs(tag, @XY6["#{loc}"])
832      end
833    }
834    if s % 3 == 1
835      first = (s + 2)/3
836      i = first
837      loop {
838        tag = "I6_b#{i}"
839        break if @canvas.find_withtag(tag).empty?
840        loc = first - i
841        move_abs(tag, @XY6["#{loc}"])
842        i += 1
843      }
844    end
845    if s >= 3  # Rotate the motor
846      idx = s % 3
847      draw6a(12 + s * 15)
848    end
849    return((s == 3)? 3 : 1)
850  end
851
852  # On/off switch
853  def draw7
854    color = @C['7']
855    xy = [198, 306, 277, 374]  # Box
856    TkcRectangle.new(@canvas, xy, :outline=>@C['fg'], :width=>2,
857                     :fill=>color, :tag=>'I7z')
858    @canvas.lower('I7z')
859    xy = [275, 343, 230, 349]
860    TkcLine.new(@canvas, xy, :tag=>'I7', :fill=>@C['fg'], :arrow=>:last,
861                :arrowshape=>[23, 23, 8], :width=>6)
862    xy = [225, 324]  # On button
863    x, y = xy
864    TkcOval.new(@canvas, box(x, y, 3), :fill=>@C['fg'], :outline=>@C['fg'])
865    xy = [218, 323]  # On text
866    font = ['Times Roman', 8]
867    TkcText.new(@canvas, xy, :text=>'on', :anchor=>:e,
868                :fill=>@C['fg'], :font=>font)
869    xy = [225, 350]  # Off button
870    x, y = xy
871    TkcOval.new(@canvas, box(x, y, 3), :fill=>@C['fg'], :outline=>@C['fg'])
872    xy = [218, 349]  # Off text
873    TkcText.new(@canvas, xy, :text=>'off', :anchor=>:e,
874                :fill=>@C['fg'], :font=>font)
875  end
876
877  def move7(step = nil)
878    step = get_step(7, step)
879
880    numsteps = 30
881    return 0 if step > numsteps
882    beta = 30.0 / numsteps
883    rotate_item('I7', 275, 343, beta)
884
885    return((step == numsteps)? 3: 1)
886  end
887
888  # Electricity to the fan
889  def draw8
890    sine([271, 248, 271, 306], 5, 8, :tag=>'I8_s', :fill=>@C['8'], :width=>3)
891  end
892
893  def move8(step = nil)
894    step = get_step(8, step)
895
896    return 0 if step > 3
897    if step == 0
898      sparkle(anchor('I8_s', :s), 'I8')
899      return 1
900    elsif step == 1
901      move_abs('I8', anchor('I8_s', :c))
902    elsif step == 2
903      move_abs('I8', anchor('I8_s', :n))
904    else
905      @canvas.delete('I8')
906    end
907    return((step == 2)? 3: 1)
908  end
909
910  # Fan
911  def draw9
912    color = @C['9']
913    xy = [266, 194, 310, 220]
914    TkcOval.new(@canvas, xy, :outline=>color, :fill=>color)
915    xy = [280, 209, 296, 248]
916    TkcOval.new(@canvas, xy, :outline=>color, :fill=>color)
917    xy = [
918      288, 249, 252, 249, 260, 240, 280, 234,
919      296, 234, 316, 240, 324, 249, 288, 249
920    ]
921    TkcPolygon.new(@canvas, xy, :fill=>color, :smooth=>true)
922
923    xy = [248, 205, 265, 214, 264, 205, 265, 196]  # Spinner
924    TkcPolygon.new(@canvas, xy, :fill=>color)
925
926    xy = [255, 206, 265, 234]  # Fan blades
927    TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'],
928                :width=>3, :tag=>'I9_0')
929    xy = [255, 176, 265, 204]
930    TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'],
931                :width=>3, :tag=>'I9_0')
932    xy = [255, 206, 265, 220]
933    TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'],
934                :width=>1, :tag=>'I9_1')
935    xy = [255, 190, 265, 204]
936    TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'],
937                :width=>1, :tag=>'I9_1')
938  end
939
940  def move9(step = nil)
941    step = get_step(9, step)
942
943    if (step & 1).nonzero?
944      @canvas.itemconfigure('I9_0', :width=>4)
945      @canvas.itemconfigure('I9_1', :width=>1)
946      @canvas.lower('I9_1', 'I9_0')
947    else
948      @canvas.itemconfigure('I9_0', :width=>1)
949      @canvas.itemconfigure('I9_1', :width=>4)
950      @canvas.lower('I9_0', 'I9_1')
951    end
952    return 3 if step == 0
953    return 1
954  end
955
956  # Boat
957  def draw10
958    color  = @C['10a']
959    color2 = @C['10b']
960    xy = [191, 230, 233, 230, 233, 178, 191, 178]  # Sail
961    TkcPolygon.new(@canvas, xy, :fill=>color, :width=>3, :outline=>@C['fg'],
962                   :tag=>'I10')
963    xy = box(209, 204, 31)  # Front
964    TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :style=>:pie,
965               :start=>120, :extent=>120, :tag=>'I10')
966    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc,
967               :start=>120, :extent=>120, :tag=>'I10')
968    xy = box(249, 204, 31)  # Back
969    TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>3,
970               :style=>:pie, :start=>120, :extent=>120, :tag=>'I10')
971    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc,
972               :start=>120, :extent=>120, :tag=>'I10')
973
974    xy = [200, 171, 200, 249]  # Mast
975    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I10')
976    xy = [159, 234, 182, 234]  # Bow sprit
977    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I10')
978    xy = [180, 234, 180, 251, 220, 251]  # Hull
979    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>6, :tag=>'I10')
980
981    xy = [92, 255, 221, 255]  # Waves
982    sine(xy, 2, 25, :fill=>color2, :width=>1, :tag=>'I10w')
983
984    xy = @canvas.coords('I10w')[4..-5]  # Water
985    xy.concat([222, 266, 222, 277, 99, 277])
986    TkcPolygon.new(@canvas, xy, :fill=>color2, :outline=>color2)
987    xy = [222, 266, 222, 277, 97, 277, 97, 266]  # Water bottom
988    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
989
990    xy = box(239, 262, 17)
991    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc,
992               :start=>95, :extent=>103)
993    xy = box(76, 266, 21)
994    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc,
995               :extent=>190)
996  end
997
998  def move10(step = nil)
999    step = get_step(10, step)
1000
1001    pos = [
1002      [195, 212], [193, 212], [190, 212], [186, 212], [181, 212], [176, 212],
1003      [171, 212], [166, 212], [161, 212], [156, 212], [151, 212], [147, 212],
1004      [142, 212], [137, 212], [132, 212, :x], [127, 212], [121, 212],
1005      [116, 212], [111, 212]
1006    ]
1007
1008    return 0 if step >= pos.length
1009
1010    where = pos[step]
1011    move_abs('I10', where)
1012
1013    return 3 if where[2] == :x
1014    return 1
1015  end
1016
1017  # 2nd ball drop
1018  def draw11
1019    color  = @C['11a']
1020    color2 = @C['11b']
1021    xy = [23, 264, 55, 591]  # Color the down tube
1022    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'')
1023    xy = box(71, 460, 48)    # Color the outer loop
1024    TkcOval.new(@canvas, xy, :fill=>color, :outline=>'')
1025
1026    xy = [55, 264, 55, 458]  # Top right side
1027    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
1028    xy = [55, 504, 55, 591]  # Bottom right side
1029    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
1030    xy = box(71, 460, 48)    # Outer loop
1031    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc,
1032               :start=>110, :extent=>-290, :tag=>'I11i')
1033    xy = box(71, 460, 16)    # Inner loop
1034    TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>'',
1035                :width=>3, :tag=>'I11i')
1036    TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['bg'], :width=>3)
1037
1038    xy = [23, 264, 23, 591]  # Left side
1039    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
1040    xy = box(1, 266, 23)     # Top left curve
1041    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3,
1042               :style=>:arc, :extent=>90)
1043
1044    xy = box(75, 235, 9)     # The ball
1045    TkcOval.new(@canvas, xy, :fill=>color2, :outline=>'',
1046                :width=>3, :tag=>'I11')
1047  end
1048
1049  def move11(step = nil)
1050    step = get_step(11, step)
1051
1052    pos = [
1053      [75, 235], [70, 235], [65, 237], [56, 240], [46, 247], [38, 266],
1054      [38, 296], [38, 333], [38, 399], [38, 475], [74, 496], [105, 472],
1055      [100, 437], [65, 423], [-100, -100], [38, 505], [38, 527, :x], [38, 591]
1056    ]
1057
1058    return 0 if step >= pos.length
1059    where = pos[step]
1060    move_abs('I11', where)
1061    return 3 if where[2] == :x
1062    return 1
1063  end
1064
1065  # Hand
1066  def draw12
1067    xy = [
1068      20, 637, 20, 617, 20, 610, 20, 590, 40, 590, 40, 590,
1069      60, 590, 60, 610, 60, 610
1070    ]
1071    xy.concat([60, 610, 65, 620, 60, 631])  # Thumb
1072    xy.concat([60, 631, 60, 637, 60, 662, 60, 669, 52, 669,
1073                56, 669, 50, 669, 50, 662, 50, 637])
1074
1075    y0 = 637  # Bumps for fingers
1076    y1 = 645
1077    50.step(21, -10){|x|
1078      x1 = x - 5
1079      x2 = x - 10
1080      xy << x << y0 << x1 << y1 << x2 << y0
1081    }
1082    TkcPolygon.new(@canvas, xy, :fill=>@C['12'], :outline=>@C['fg'],
1083                   :smooth=>true, :tag=>'I12', :width=>3)
1084  end
1085
1086  def move12(step = nil)
1087    step = get_step(12, step)
1088
1089    pos = [[42.5, 641, :x]]
1090    return 0 if step >= pos.length
1091    where = pos[step]
1092    move_abs('I12', where)
1093    return 3 if where[2] == :x
1094    return 1
1095  end
1096
1097  # Fax
1098  def draw13
1099    color = @C['13a']
1100    xy = [86, 663, 149, 663, 149, 704, 50, 704, 50, 681, 64, 681, 86, 671]
1101    xy2 = [
1102      784, 663, 721, 663, 721, 704, 820, 704, 820, 681, 806, 681, 784, 671
1103    ]
1104    radii = [2, 9, 9, 8, 5, 5, 2]
1105
1106    round_poly(@canvas, xy, radii, :width=>3,
1107               :outline=>@C['fg'], :fill=>color)
1108    round_poly(@canvas, xy2, radii, :width=>3,
1109               :outline=>@C['fg'], :fill=>color)
1110
1111    xy = [56, 677]
1112    x, y = xy
1113    TkcRectangle.new(@canvas, box(x, y, 4), :fill=>'', :outline=>@C['fg'],
1114                     :width=>3, :tag=>'I13')
1115    xy = [809, 677]
1116    x, y = xy
1117    TkcRectangle.new(@canvas, box(x, y, 4), :fill=>'', :outline=>@C['fg'],
1118                     :width=>3, :tag=>'I13R')
1119
1120    xy = [112, 687]  # Label
1121    TkcText.new(@canvas, xy, :text=>'FAX', :fill=>@C['fg'],
1122                :font=>['Times Roman', 12, :bold])
1123    xy = [762, 687]
1124    TkcText.new(@canvas, xy, :text=>'FAX', :fill=>@C['fg'],
1125                :font=>['Times Roman', 12, :bold])
1126
1127    xy = [138, 663, 148, 636, 178, 636]  # Paper guide
1128    TkcLine.new(@canvas, xy, :smooth=>true, :fill=>@C['fg'], :width=>3)
1129    xy = [732, 663, 722, 636, 692, 636]
1130    TkcLine.new(@canvas, xy, :smooth=>true, :fill=>@C['fg'], :width=>3)
1131
1132    sine([149, 688, 720, 688], 5, 15,
1133         :tag=>'I13_s', :fill=>@C['fg'],  :width=>3)
1134  end
1135
1136  def move13(step = nil)
1137    step = get_step(13, step)
1138
1139    numsteps = 7
1140
1141    if step == numsteps + 2
1142      move_abs('I13_star', [-100, -100])
1143      @canvas.itemconfigure('I13R', :fill=>@C['13b'], :width=>2)
1144      return 2
1145    end
1146    if step == 0  # Button down
1147      @canvas.delete('I13')
1148      sparkle([-100, -100], 'I13_star')  # Create off screen
1149      return 1
1150    end
1151    x0, y0 = anchor('I13_s', :w)
1152    x1, y1 = anchor('I13_s', :e)
1153    x = x0 + (x1 - x0) * (step - 1) / numsteps.to_f
1154    move_abs('I13_star', [x, y0])
1155    return 1
1156  end
1157
1158  # Paper in fax
1159  def draw14
1160    color = @C['14']
1161    xy = [102, 661, 113, 632, 130, 618]  # Left paper edge
1162    TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color,
1163                :width=>3, :tag=>'I14L_0')
1164    xy = [148, 629, 125, 640, 124, 662]  # Right paper edge
1165    TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color,
1166                :width=>3, :tag=>'I14L_1')
1167    draw14a('L')
1168
1169    xy = [
1170      768.0, 662.5, 767.991316225, 662.433786215, 767.926187912, 662.396880171
1171    ]
1172    TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color,
1173                :width=>3, :tag=>'I14R_0')
1174    @canvas.lower('I14R_0')
1175    # NB. these numbers are VERY sensitive, you must start with final size
1176    # and shrink down to get the values
1177    xy = [
1178      745.947897349, 662.428358855, 745.997829056, 662.452239237, 746.0, 662.5
1179    ]
1180    TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color,
1181                :width=>3, :tag=>'I14R_1')
1182    @canvas.lower('I14R_1')
1183  end
1184
1185  def draw14a(side)
1186    color = @C['14']
1187    xy = @canvas.coords("I14#{side}_0")
1188    xy2 = @canvas.coords("I14#{side}_1")
1189    x0, y0, x1, y1, x2, y2 = xy
1190    x3, y3, x4, y4, x5, y5 = xy2
1191
1192    zz = [
1193      x0, y0, x0, y0, xy, x2, y2, x2, y2,
1194      x3, y3, x3, y3, xy2, x5, y5, x5, y5
1195    ].flatten
1196    @canvas.delete("I14#{side}")
1197    TkcPolygon.new(@canvas, zz, :tag=>"I14#{side}", :smooth=>true,
1198                   :fill=>color, :outline=>color, :width=>3)
1199    @canvas.lower("I14#{side}")
1200  end
1201
1202  def move14(step = nil)
1203    step = get_step(14, step)
1204
1205    # Paper going down
1206    sc = 0.9 - 0.05*step
1207    if sc < 0.3
1208      @canvas.delete('I14L')
1209      return 0
1210    end
1211
1212    ox, oy = @canvas.coords('I14L_0')
1213    @canvas.scale('I14L_0', ox, oy, sc, sc)
1214    ox, oy = @canvas.coords('I14L_1')[-2..-1]
1215    @canvas.scale('I14L_1', ox, oy, sc, sc)
1216    draw14a('L')
1217
1218    # Paper going up
1219    sc = 0.35 + 0.05*step
1220    sc = 1/sc
1221
1222    ox, oy = @canvas.coords('I14R_0')
1223    @canvas.scale('I14R_0', ox, oy, sc, sc)
1224    ox, oy = @canvas.coords('I14R_1')[-2..-1]
1225    @canvas.scale('I14R_1', ox, oy, sc, sc)
1226    draw14a('R')
1227
1228    return((step == 10)? 3: 1)
1229  end
1230
1231  # Light beam
1232  def draw15
1233    color = @C['15a']
1234    xy = [824, 599, 824, 585, 820, 585, 829, 585]
1235    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I15a')
1236    xy = [789, 599, 836, 643]
1237    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3)
1238    xy = [778, 610, 788, 632]
1239    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3)
1240    xy = [766, 617, 776, 625]
1241    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3)
1242
1243    xy = [633, 600, 681, 640]
1244    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3)
1245    xy = [635, 567, 657, 599]
1246    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2)
1247    xy = [765, 557, 784, 583]
1248    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2)
1249
1250    sine([658, 580, 765, 580], 3, 15,
1251         :tag=>'I15_s', :fill=>@C['fg'], :width=>3)
1252  end
1253
1254  def move15a
1255    color = @C['15b']
1256    @canvas.scale('I15a', 824, 599, 1, 0.3)  # Button down
1257    xy = [765, 621, 681, 621]
1258    TkcLine.new(@canvas, xy, :dash=>'-', :width=>3, :fill=>color, :tag=>'I15')
1259  end
1260
1261  def move15(step = nil)
1262    step = get_step(15, step)
1263
1264    numsteps = 6
1265
1266    if step == numsteps + 2
1267      move_abs('I15_star', [-100, -100])
1268      return 2
1269    end
1270    if step == 0  # Break the light beam
1271      sparkle([-100, -100], 'I15_star')
1272      xy = [765, 621, 745, 621]
1273      @canvas.coords('I15', xy)
1274      return 1
1275    end
1276    x0, y0 = anchor('I15_s', :w)
1277    x1, y1 = anchor('I15_s', :e)
1278    x = x0 + (x1 - x0) * (step - 1) / numsteps.to_f
1279    move_abs('I15_star', [x, y0])
1280    return 1
1281  end
1282
1283  # Bell
1284  def draw16
1285    color = @C['16']
1286    xy = [722, 485, 791, 556]
1287    TkcRectangle.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], :width=>3)
1288    xy = box(752, 515, 25)  # Bell
1289    TkcOval.new(@canvas, xy, :fill=>color, :outline=>'black',
1290                :tag=>'I16b', :width=>2)
1291    xy = box(752, 515, 5)   # Bell button
1292    TkcOval.new(@canvas, xy, :fill=>'black', :outline=>'black', :tag=>'I16b')
1293
1294    xy = [784, 523, 764, 549]  # Clapper
1295    TkcLine.new(@canvas, xy, :width=>3, :tag=>'I16c', :fill=>@C['fg'])
1296    xy = box(784, 523, 4)
1297    TkcOval.new(@canvas, xy, :fill=>@C['fg'], :outline=>@C['fg'], :tag=>'I16d')
1298  end
1299
1300  def move16(step = nil)
1301    step = get_step(16, step)
1302
1303    # Note: we never stop
1304    ox, oy = [760, 553]
1305    if (step & 1).nonzero?
1306      beta = 12
1307      @canvas.move('I16b', 3, 0)
1308    else
1309      beta = -12
1310      @canvas.move('I16b', -3, 0)
1311    end
1312    rotate_item('I16c', ox, oy, beta)
1313    rotate_item('I16d', ox, oy, beta)
1314
1315    return ((step == 1)? 3: 1)
1316  end
1317
1318  # Cat
1319  def draw17
1320    color = @C['17']
1321
1322    xy = [584, 556, 722, 556]
1323    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
1324    xy = [584, 485, 722, 485]
1325    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3)
1326
1327    xy = [664, 523, 717, 549]  # Body
1328    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :fill=>color, :width=>3,
1329               :style=>:chord, :start=>128, :extent=>260, :tag=>'I17')
1330
1331    xy = [709, 554, 690, 543]  # Paw
1332    TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>color,
1333                :width=>3, :tag=>'I17')
1334    xy = [657, 544, 676, 555]
1335    TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>color,
1336                :width=>3, :tag=>'I17')
1337
1338    xy = box(660, 535, 15)     # Lower face
1339    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc,
1340               :start=>150, :extent=>240, :tag=>'I17_')
1341    TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1,
1342               :style=>:chord, :start=>150, :extent=>240, :tag=>'I17_')
1343    xy = [674, 529, 670, 513, 662, 521, 658, 521, 650, 513, 647, 529]  # Ears
1344    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1345    TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'', :width=>1,
1346                   :tag=>['I17_', 'I17_c'])
1347    xy = [652, 542, 628, 539]  # Whiskers
1348    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1349    xy = [652, 543, 632, 545]
1350    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1351    xy = [652, 546, 632, 552]
1352    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1353
1354    xy = [668, 543, 687, 538]
1355    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3,
1356                :tag=>['I17_', 'I17_w'])
1357    xy = [668, 544, 688, 546]
1358    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3,
1359                :tag=>['I17_', 'I17_w'])
1360    xy = [668, 547, 688, 553]
1361    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3,
1362                :tag=>['I17_', 'I17_w'])
1363
1364    xy = [649, 530, 654, 538, 659, 530]  # Left eye
1365    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2,
1366                :smooth=>true, :tag=>'I17')
1367    xy = [671, 530, 666, 538, 661, 530]  # Right eye
1368    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2,
1369                :smooth=>true, :tag=>'I17')
1370    xy = [655, 543, 660, 551, 665, 543]  # Mouth
1371    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2,
1372                :smooth=>true, :tag=>'I17')
1373  end
1374
1375  def move17(step = nil)
1376    step = get_step(17, step)
1377
1378    if step == 0
1379      @canvas.delete('I17')  # Delete most of the cat
1380      xy = [655, 543, 660, 535, 665, 543]  # Mouth
1381      TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3,
1382                  :smooth=>true, :tag=>'I17_')
1383      xy = box(654, 530, 4)  # Left eye
1384      TkcOval.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :fill=>'',
1385                  :tag=>'I17_')
1386      xy = box(666, 530, 4)  # Right eye
1387      TkcOval.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :fill=>'',
1388                  :tag=>'I17_')
1389
1390      @canvas.move('I17_', 0, -20) # Move face up
1391      xy = [652, 528, 652, 554]    # Front leg
1392      TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1393      xy = [670, 528, 670, 554]    # 2nd front leg
1394      TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1395
1396      xy = [ # Body
1397        675, 506, 694, 489, 715, 513, 715, 513, 715, 513, 716, 525,
1398        716, 525, 716, 525, 706, 530, 695, 530, 679, 535, 668, 527,
1399        668, 527, 668, 527, 675, 522, 676, 517, 677, 512
1400      ]
1401      TkcPolygon.new(@canvas, xy, :fill=>@canvas.itemcget('I17_c', :fill),
1402                     :outline=>@C['fg'], :width=>3, :smooth=>true,
1403                     :tag=>'I17_')
1404      xy = [716, 514, 716, 554]  # Back leg
1405      TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1406      xy = [694, 532, 694, 554]  # 2nd back leg
1407      TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_')
1408      xy = [715, 514, 718, 506, 719, 495, 716, 488]  # Tail
1409      TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3,
1410                  :smooth=>true, :tag=>'I17_')
1411
1412      @canvas.raise('I17w')       # Make whiskers visible
1413      @canvas.move('I17_', -5, 0) # Move away from the wall a bit
1414      return 2
1415    end
1416    return 0
1417  end
1418
1419  # Sling shot
1420  def draw18
1421    color = @C['18']
1422    xy = [721, 506, 627, 506]  # Sling hold
1423    TkcLine.new(@canvas, xy, :width=>4, :fill=>@C['fg'], :tag=>'I18')
1424
1425    xy = [607, 500, 628, 513]  # Sling rock
1426    TkcOval.new(@canvas, xy, :fill=>color, :outline=>'', :tag=>'I18a')
1427
1428    xy = [526, 513, 606, 507, 494, 502]  # Sling band
1429    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>4, :tag=>'I18b')
1430    xy = [485, 490, 510, 540, 510, 575, 510, 540, 535, 491]  # Sling
1431    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>6)
1432  end
1433
1434  def move18(step = nil)
1435    step = get_step(18, step)
1436
1437    pos = [
1438      [587, 506], [537, 506], [466, 506], [376, 506], [266, 506, :x],
1439      [136, 506], [16, 506], [-100, -100]
1440    ]
1441
1442    b = []
1443    b[0] = [490, 502, 719, 507, 524, 512]  # Band collapsing
1444    b[1] = [
1445      491, 503, 524, 557, 563, 505, 559, 496, 546, 506, 551, 525,
1446      553, 536, 538, 534, 532, 519, 529, 499
1447    ]
1448    b[2] = [
1449      491, 503, 508, 563, 542, 533, 551, 526, 561, 539, 549, 550, 530, 500
1450    ]
1451    b[3] = [
1452      491, 503, 508, 563, 530, 554, 541, 562, 525, 568, 519, 544, 530, 501
1453    ]
1454
1455    return 0 if step >= pos.length
1456
1457    if step == 0
1458      @canvas.delete('I18')
1459      @canvas.itemconfigure('I18b', :smooth=>true)
1460    end
1461    if b[step]
1462      @canvas.coords('I18b', b[step])
1463    end
1464
1465    where = pos[step]
1466    move_abs('I18a', where)
1467    return 3 if where[2] == :x
1468    return 1
1469  end
1470
1471  # Water pipe
1472  def draw19
1473    color = @C['19']
1474    xx = [[249, 181], [155, 118], [86, 55], [22, 0]]
1475    xx.each{|x1, x2|
1476      TkcRectangle.new(@canvas, x1, 453, x2, 467,
1477                       :fill=>color, :outline=>'', :tag=>'I19')
1478      TkcLine.new(@canvas, x1, 453, x2, 453,
1479                  :fill=>@C['fg'], :width=>1) # Pipe top
1480      TkcLine.new(@canvas, x1, 467, x2, 467,
1481                  :fill=>@C['fg'], :width=>1) # Pipe bottom
1482    }
1483    @canvas.raise('I11i')
1484
1485    xy = box(168, 460, 16)  # Bulge by the joint
1486    TkcOval.new(@canvas, xy, :fill=>color, :outline=>'')
1487    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, :style=>:arc,
1488               :start=>21, :extent=>136)
1489    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, :style=>:arc,
1490               :start=>-21, :extent=>-130)
1491
1492    xy = [249, 447, 255, 473]  # First joint 26x6
1493    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1)
1494
1495    xy = box(257, 433, 34)     # Bend up
1496    TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1,
1497               :style=>:pie, :start=>0, :extent=>-91)
1498    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1,
1499               :style=>:arc, :start=>0, :extent=>-90)
1500    xy = box(257, 433, 20)
1501    TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1,
1502               :style=>:pie, :start=>0, :extent=>-92)
1503    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1,
1504               :style=>:arc, :start=>0, :extent=>-90)
1505    xy = box(257, 421, 34)     # Bend left
1506    TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1,
1507               :style=>:pie, :start=>0, :extent=>91)
1508    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1,
1509               :style=>:arc, :start=>0, :extent=>90)
1510    xy = box(257, 421, 20)
1511    TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1,
1512               :style=>:pie, :start=>0, :extent=>90)
1513    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1,
1514               :style=>:arc, :start=>0, :extent=>90)
1515    xy = box(243, 421, 34)     # Bend down
1516    TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1,
1517               :style=>:pie, :start=>90, :extent=>90)
1518    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1,
1519               :style=>:arc, :start=>90, :extent=>90)
1520    xy = box(243, 421, 20)
1521    TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1,
1522               :style=>:pie, :start=>90, :extent=>90)
1523    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1,
1524               :style=>:arc, :start=>90, :extent=>90)
1525
1526    xy = [270, 427, 296, 433]  # 2nd joint bottom
1527    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1)
1528    xy = [270, 421, 296, 427]  # 2nd joint top
1529    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1)
1530    xy = [249, 382, 255, 408]  # Third joint right
1531    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1)
1532    xy = [243, 382, 249, 408]  # Third joint left
1533    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1)
1534    xy = [203, 420, 229, 426]  # Last joint
1535    TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1)
1536
1537    xy = box(168, 460, 6)      # Handle joint
1538    TkcOval.new(@canvas, xy, :fill=>@C['fg'], :outline=>'', :tag=>'I19a')
1539    xy = [168, 460, 168, 512]  # Handle bar
1540    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>5, :tag=>'I19b')
1541  end
1542
1543  def move19(step = nil)
1544    step = get_step(19, step)
1545
1546     angles = [30, 30, 30]
1547    return 2 if step == angles.length
1548    ox, oy = centroid('I19a')
1549    rotate_item('I19b', ox, oy, angles[step])
1550
1551    return 1
1552  end
1553
1554  # Water pouring
1555  def draw20
1556    # do nothing
1557  end
1558
1559  def move20(step = nil)
1560    step = get_step(20, step)
1561
1562    pos  = [451, 462, 473, 484, 496, 504, 513, 523, 532]
1563    freq  = [20,  40,  40,  40,  40,  40,  40,  40,  40]
1564    pos = [
1565      [451, 20], [462, 40], [473, 40], [484, 40], [496, 40],
1566      [504, 40], [513, 40], [523, 40], [532, 40, :x]
1567    ]
1568    return 0 if step >= pos.length
1569
1570    @canvas.delete('I20')
1571    where = pos[step]
1572    y, f = where
1573    h20(y, f)
1574    return 3 if where[2] == :x
1575    return 1
1576  end
1577
1578  def h20(y, f)
1579    color = @C['20']
1580    @canvas.delete('I20')
1581
1582    sine([208, 428, 208, y], 4, f, :tag=>['I20', 'I20s'],
1583         :width=>3, :fill=>color, :smooth=>true)
1584    TkcLine.new(@canvas, @canvas.coords('I20s'), :width=>3,
1585                :fill=>color, :smooth=>1, :tag=>['I20', 'I20a'])
1586    TkcLine.new(@canvas, @canvas.coords('I20s'), :width=>3,
1587                :fill=>color, :smooth=>1, :tag=>['I20', 'I20b'])
1588    @canvas.move('I20a', 8, 0)
1589    @canvas.move('I20b', 16, 0)
1590  end
1591
1592  # Bucket
1593  def draw21
1594    color = @C['21']
1595    xy = [217, 451, 244, 490]  # Right handle
1596    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21_a')
1597    xy = [201, 467, 182, 490]  # Left handle
1598    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21_a')
1599
1600    xy = [245, 490, 237, 535]  # Right side
1601    xy2 = [189, 535, 181, 490] # Left side
1602    TkcPolygon.new(@canvas, xy + xy2, :fill=>color, :outline=>'',
1603                   :tag=>['I21', 'I21f'])
1604    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21')
1605    TkcLine.new(@canvas, xy2, :fill=>@C['fg'], :width=>2, :tag=>'I21')
1606
1607    xy = [182, 486, 244, 498]  # Top
1608    TkcOval.new(@canvas, xy, :fill=>color, :outline=>'', :width=>2,
1609                :tag=>['I21', 'I21f'])
1610    TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], :width=>2,
1611                :tag=>['I21', 'I21t'])
1612    xy = [189, 532, 237, 540]  # Bottom
1613    TkcOval.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2,
1614                :tag=>['I21', 'I21b'])
1615  end
1616
1617  def move21(step = nil)
1618    step = get_step(21, step)
1619
1620    numsteps = 30
1621    return 0 if step >= numsteps
1622
1623    x1, y1, x2, y2 = @canvas.coords('I21b')
1624    # lx1, ly1, lx2, ly2 = @canvas.coords('I21t')
1625    lx1, ly1, lx2, ly2 = [183, 492, 243, 504]
1626
1627    f = step / numsteps.to_f
1628    y2 = y2 - 3
1629    xx1 = x1 + (lx1 - x1) * f
1630    yy1 = y1 + (ly1 - y1) * f
1631    xx2 = x2 + (lx2 - x2) * f
1632    yy2 = y2 + (ly2 - y2) * f
1633
1634    @canvas.itemconfigure('I21b', :fill=>@C['20'])
1635    @canvas.delete('I21w')
1636    TkcPolygon.new(@canvas, x2, y2, x1, y1, xx1, yy1, xx2, yy1,
1637                   :tag=>['I21', 'I21w'], :outline=>'', :fill=>@C['20'])
1638    @canvas.lower('I21w', 'I21')
1639    @canvas.raise('I21b')
1640    @canvas.lower('I21f')
1641
1642    return((step == numsteps - 1)? 3: 1)
1643  end
1644
1645  # Bucket drop
1646  def draw22
1647    # do nothing
1648  end
1649
1650  def move22(step = nil)
1651    step = get_step(22, step)
1652    pos = [[213, 513], [213, 523], [213, 543, :x], [213, 583], [213, 593]]
1653
1654    @canvas.itemconfigure('I21f', :fill=>@C['22']) if step == 0
1655    return 0 if step >= pos.length
1656    where = pos[step]
1657    move_abs('I21', where)
1658    h20(where[1], 40)
1659    @canvas.delete('I21_a')  # Delete handles
1660
1661    return 3 if where[2] == :x
1662    return 1
1663  end
1664
1665  # Blow dart
1666  def draw23
1667    color  = @C['23a']
1668    color2 = @C['23b']
1669    color3 = @C['23c']
1670
1671    xy = [185, 623, 253, 650]  # Block
1672    TkcRectangle.new(@canvas, xy, :fill=>'black', :outline=>@C['fg'],
1673                     :width=>2, :tag=>'I23a')
1674    xy = [187, 592, 241, 623]  # Balloon
1675    TkcOval.new(@canvas, xy, :outline=>'', :fill=>color, :tag=>'I23b')
1676    TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :tag=>'I23b',
1677               :style=>:arc, :start=>12, :extent=>336)
1678    xy = [239, 604, 258, 589, 258, 625, 239, 610]  # Balloon nozzle
1679    TkcPolygon.new(@canvas, xy, :outline=>'', :fill=>color, :tag=>'I23b')
1680    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23b')
1681
1682    xy = [285, 611, 250, 603]  # Dart body
1683    TkcOval.new(@canvas, xy, :fill=>color2, :outline=>@C['fg'],
1684                :width=>3, :tag=>'I23d')
1685    xy = [249, 596, 249, 618, 264, 607, 249, 596]  # Dart tail
1686    TkcPolygon.new(@canvas, xy, :fill=>color3, :outline=>@C['fg'],
1687                   :width=>3, :tag=>'I23d')
1688    xy = [249, 607, 268, 607]  # Dart detail
1689    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23d')
1690    xy = [285, 607, 305, 607]  # Dart needle
1691    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23d')
1692  end
1693
1694  def move23(step = nil)
1695    step = get_step(23, step)
1696
1697    pos = [
1698      [277, 607], [287, 607], [307, 607, :x], [347, 607], [407, 607],
1699      [487, 607], [587, 607], [687, 607], [787, 607], [-100, -100]
1700    ]
1701
1702    return 0 if step >= pos.length
1703    if step <= 1
1704      ox, oy = anchor('I23a', :n)
1705      @canvas.scale('I23b', ox, oy, 0.9, 0.5)
1706    end
1707    where = pos[step]
1708    move_abs('I23d', where)
1709
1710    return 3 if where[2] == :x
1711    return 1
1712  end
1713
1714  # Balloon
1715  def draw24
1716    color = @C['24a']
1717    xy = [366, 518, 462, 665]  # Balloon
1718    TkcOval.new(@canvas, xy, :fill=>color, :outline=>@C['fg'],
1719                :width=>3, :tag=>'I24')
1720    xy = [414, 666, 414, 729]  # String
1721    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I24')
1722    xy = [410, 666, 404, 673, 422, 673, 418, 666]  # Nozzle
1723    TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>@C['fg'],
1724                   :width=>3, :tag=>'I24')
1725
1726    xy = [387, 567, 390, 549, 404, 542]  # Reflections
1727    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true,
1728                :width=>2, :tag=>'I24')
1729    xy = [395, 568, 399, 554, 413, 547]
1730    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true,
1731                :width=>2, :tag=>'I24')
1732    xy = [403, 570, 396, 555, 381, 553]
1733    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true,
1734                :width=>2, :tag=>'I24')
1735    xy = [408, 564, 402, 547, 386, 545]
1736    TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true,
1737                :width=>2, :tag=>'I24')
1738  end
1739
1740  def move24(step = nil)
1741    step = get_step(24, step)
1742
1743    return 0 if step > 4
1744    return 2 if step == 4
1745
1746    if step == 0
1747      @canvas.delete('I24')  # Exploding balloon
1748      xy = [
1749        347, 465, 361, 557, 271, 503, 272, 503, 342, 574, 259, 594,
1750        259, 593, 362, 626, 320, 737, 320, 740, 398, 691, 436, 738,
1751        436, 739, 476, 679, 528, 701, 527, 702, 494, 627, 548, 613,
1752        548, 613, 480, 574, 577, 473, 577, 473, 474, 538, 445, 508,
1753        431, 441, 431, 440, 400, 502, 347, 465, 347, 465
1754      ]
1755      TkcPolygon.new(@canvas, xy, :tag=>'I24', :fill=>@C['24b'],
1756                     :outline=>@C['24a'], :width=>10, :smooth=>true)
1757      msg = Tk.subst(@S['message'].value)
1758      TkcText.new(@canvas, centroid('I24'), :text=>msg, :tag=>['I24', 'I24t'],
1759                  :justify=>:center, :font=>['Times Roman', 18, :bold])
1760      return 1
1761    end
1762
1763    @canvas.itemconfigure('I24t', :font=>['Times Roman', 18 + 6*step, :bold])
1764    @canvas.move('I24', 0, -60)
1765    ox, oy = centroid('I24')
1766    @canvas.scale('I24', ox, oy, 1.25, 1.25)
1767    return 1
1768  end
1769
1770  # Displaying the message
1771  def move25(step = nil)
1772    step = get_step(25, step)
1773
1774    if step == 0
1775      @XY['25'] = Tk::Clock.clicks(:miliseconds)
1776      return 1
1777    end
1778    elapsed = Tk::Clock.clicks(:miliseconds) - @XY['25']
1779    return 1 if elapsed < 5000
1780    return 2
1781  end
1782
1783  # Collapsing balloon
1784  def move26(step = nil)
1785    step = get_step(26, step)
1786
1787    if step >= 3
1788      @canvas.delete('I24', 'I26')
1789      TkcText.new(@canvas, 430, 740, :anchor=>:s, :tag=>'I26',
1790                  :text=>'click to continue',
1791                  :font=>['Times Roman', 24, :bold])
1792      @canvas.bind('1', proc{reset})
1793      return 4
1794    end
1795
1796    ox, oy = centroid('I24')
1797    @canvas.scale('I24', ox, oy, 0.8, 0.8)
1798    @canvas.move('I24', 0, 60)
1799    @canvas.itemconfigure('I24t', :font=>['Times Roman', 30 - 6*step, :bold])
1800    return 1
1801  end
1802
1803  ################################################################
1804  #
1805  # Helper functions
1806  #
1807  def box(x, y, r)
1808    [x - r, y - r, x + r, y + r]
1809  end
1810
1811  def move_abs(item, xy)
1812    x, y = xy
1813    ox, oy = centroid(item)
1814    dx = x - ox
1815    dy = y - oy
1816    @canvas.move(item, dx, dy)
1817  end
1818
1819  def rotate_item(item, ox, oy, beta)
1820    xy = @canvas.coords(item)
1821    xy2 = []
1822    0.step(xy.length - 1, 2){|idx|
1823      x, y = xy[idx, 2]
1824      xy2.concat(rotate_c(x, y, ox, oy, beta))
1825    }
1826    @canvas.coords(item, xy2)
1827  end
1828
1829  def rotate_c(x, y, ox, oy, beta)
1830    # rotates vector (ox,oy)->(x,y) by beta degrees clockwise
1831
1832    x -= ox    # Shift to origin
1833    y -= oy
1834
1835    beta = beta * Math.atan(1) * 4 / 180.0        # Radians
1836    xx = x * Math.cos(beta) - y * Math.sin(beta)  # Rotate
1837    yy = x * Math.sin(beta) + y * Math.cos(beta)
1838
1839    xx += ox  # Shift back
1840    yy += oy
1841
1842    [xx, yy]
1843  end
1844
1845  def reset
1846    draw_all
1847    @canvas.bind_remove('1')
1848    @S['mode'].value = :MSTART
1849    @S['active'] = [0]
1850  end
1851
1852  # Each Move## keeps its state info in STEP, this retrieves and increments it
1853  def get_step(who, step)
1854    if step
1855      @STEP[who] = step
1856    else
1857      if !@STEP.exist?(who) || @STEP[who] == ""
1858        @STEP[who] = 0
1859      else
1860        @STEP[who] += 1
1861      end
1862    end
1863    @STEP[who]
1864  end
1865
1866  def reset_step
1867    @S['cnt'].value = 0
1868    @STEP.keys.each{|k| @STEP[k] = ''}
1869  end
1870
1871  def sine(xy0, amp, freq, opts = {})
1872    x0, y0, x1, y1 = xy0
1873    step = 2
1874    xy = []
1875    if y0 == y1  # Horizontal
1876      x0.step(x1, step){|x|
1877        beta = (x - x0) * 2 * Math::PI / freq
1878        y = y0 + amp * Math.sin(beta)
1879        xy << x << y
1880      }
1881    else
1882      y0.step(y1, step){|y|
1883        beta = (y - y0) * 2 * Math::PI / freq
1884        x = x0 + amp * Math.sin(beta)
1885        xy << x << y
1886      }
1887    end
1888    TkcLine.new(@canvas, xy, opts)
1889  end
1890
1891  def round_rect(xy, radius, opts={})
1892    x0, y0, x3, y3 = xy
1893    r = @canvas.winfo_pixels(radius)
1894    d = 2 * r
1895
1896    # Make sure that the radius of the curve is less than 3/8 size of the box!
1897    maxr = 0.75
1898    if d > maxr * (x3 - x0)
1899      d = maxr * (x3 - x0)
1900    end
1901    if d > maxr * (y3 - y0)
1902      d = maxr * (y3 - y0)
1903    end
1904
1905    x1 = x0 + d
1906    x2 = x3 - d
1907    y1 = y0 + d
1908    y2 = y3 - d
1909
1910    xy = [x0, y0, x1, y0, x2, y0, x3, y0, x3, y1, x3, y2]
1911    xy.concat([x3, y3, x2, y3, x1, y3, x0, y3, x0, y2, x0, y1])
1912    return xy
1913  end
1914
1915  def round_poly(canv, xy, radii, opts)
1916    lenXY = xy.length
1917    lenR = radii.length
1918    if lenXY != 2*lenR
1919      raise "wrong number of vertices and radii"
1920    end
1921
1922    knots = []
1923    x0 = xy[-2]; y0 = xy[-1]
1924    x1 = xy[0];  y1 = xy[1]
1925    xy << xy[0] << xy[1]
1926
1927    0.step(lenXY - 1, 2){|i|
1928      radius = radii[i/2]
1929      r = canv.winfo_pixels(radius)
1930
1931      x2 = xy[i+2];  y2 = xy[i+3]
1932      z = _round_poly2(x0, y0, x1, y1, x2, y2, r)
1933      knots.concat(z)
1934
1935      x0 = x1;  y0 = y1
1936      x1 = x2;  y1 = y2
1937    }
1938    TkcPolygon.new(canv, knots, {:smooth=>true}.update(opts))
1939  end
1940
1941  def _round_poly2(x0, y0, x1, y1, x2, y2, radius)
1942    d = 2 * radius
1943    maxr = 0.75
1944
1945    v1x = x0 - x1
1946    v1y = y0 - y1
1947    v2x = x2 - x1
1948    v2y = y2 - y1
1949
1950    vlen1 = Math.sqrt(v1x*v1x + v1y*v1y)
1951    vlen2 = Math.sqrt(v2x*v2x + v2y*v2y)
1952
1953    if d > maxr * vlen1
1954      d = maxr * vlen1
1955    end
1956    if d > maxr * vlen2
1957      d = maxr * vlen2
1958    end
1959
1960    xy = []
1961    xy << (x1 + d * v1x / vlen1) << (y1 + d * v1y / vlen1)
1962    xy << x1 << y1
1963    xy << (x1 + d * v2x / vlen2) << (y1 + d * v2y / vlen2)
1964
1965    return xy
1966  end
1967
1968  def sparkle(oxy, tag)
1969    xy = [
1970      [299, 283], [298, 302], [295, 314], [271, 331],
1971      [239, 310], [242, 292], [256, 274], [281, 273]
1972    ]
1973    xy.each{|x, y|
1974      TkcLine.new(@canvas, 271, 304, x, y,
1975                  :fill=>'white', :width=>3, :tag=>tag)
1976    }
1977    move_abs(tag, oxy)
1978  end
1979
1980  def centroid(item)
1981    anchor(item, :c)
1982  end
1983
1984  def anchor(item, where)
1985    x1, y1, x2, y2 = @canvas.bbox(item)
1986    case(where)
1987    when :n
1988      y = y1
1989    when :s
1990      y = y2
1991    else
1992      y = (y1 + y2) / 2.0
1993    end
1994    case(where)
1995    when :w
1996      x = x1
1997    when :e
1998      x = x2
1999    else
2000      x = (x1 + x2) / 2.0
2001    end
2002    return [x, y]
2003  end
2004end
2005
2006TkGoldberg_Demo.new(base_frame)
2007