Swimming ducks
animation by rotate, and joining frames along an axis two ways

Table of Contents

Ducks are iconic in graphics programming, and moving a picture by rotating it is about as old as APL. Here a family of ASCII ducks swims across a pond with nothing but rotate. The waves go the other way, which needs the ducks' frames and the water's frames joined row by row. That is written twice, as a workaround and then with the feature the demo asked for, and the two are checked to agree. The program is demos/classics/duck.xtl.

The family

A picture is a character matrix. Three rows of twenty characters: a duck and two ducklings. A backslash in a string is written twice.

family ← 3 20 r̲eshape "                 _    _    _      __(.)< (.)< (.)<    \\___) "
family

⍝ typed:
⍝   family := 3 20 r_eshape "                 _    _    _      __(.)< (.)< (.)<    \\___) "
⍝   family
                _  
 _    _      __(.)<
(.)< (.)<    \___) 

The pond is wider. Taking 32 columns along axis 2 pads the rows with blanks.

pond ← 32 t̲ake₂ family
pond

⍝ typed:
⍝   pond := 32 t_ake_2 family
⍝   pond
                _              
 _    _      __(.)<            
(.)< (.)<    \___)             

Swimming by rotate

Rotating along axis 2 slides every row at once. A negative amount moves the characters toward the end, so the ducks swim to the right, and what goes off the edge comes back on the left.

-6 o̲-₂ pond
-27 o̲-₂ pond

⍝ typed:
⍝   -6 o_-_2 pond
⍝   -27 o_-_2 pond
                      _        
       _    _      __(.)<      
      (.)< (.)<    \___)       
           _                   
 _      __(.)<              _  
(.)<    \___)              (.)<

Every frame at once

Rotate with a list of amounts gives one result per amount. With the amounts 0, -1, …, -31 that is the whole swim, a rank-3 array of 32 frames of 3 rows and 32 columns, from one expression and no loop.

frames ← (n̲eg o̲ffsets 32) o̲-₂ pond
s̲hape frames

⍝ typed:
⍝   frames := (n_eg o_ffsets 32) o_-_2 pond
⍝   s_hape frames
32 3 32

[]G_RID draws a rank-3 array as frames shown in turn, so the picture is the animation.

shown ← ⎕S̲HOW ⎕G̲RID frames

⍝ typed:
⍝   shown := []S_HOW []G_RID frames

literate-duck-swim.svg

Waves the other way

The water is one row, and every rotation of it is again one expression: a 32 by 32 matrix with a row per frame, moving left as the ducks move right.

water ← 32 r̲eshape "~  ~~ ~   "
waves ← (o̲ffsets 32) o̲- water
s̲hape waves

⍝ typed:
⍝   water := 32 r_eshape "~  ~~ ~   "
⍝   waves := (o_ffsets 32) o_- water
⍝   s_hape waves
32 32

Each frame should be the ducks' three rows with that frame's water row below them. Frame by frame, that joins along axis 2 of the frames array, not along axis 1 (which would add more frames).

The workaround: one frame at a time

Without a join along axis 2, each frame is built on its own, ducks over water by an ordinary c_at, and the frames are stacked by a recursion. The first frame is given a leading axis of length 1 so the others can be joined to it.

ᵘf̲rame ← { k → ((n̲eg k) o̲-₂ pond) c̲at k o̲- water }
ᵘs̲wim ← { k →
  f ← ᵘf̲rame k
  k = 0 ? (1 c̲at s̲hape f) r̲eshape f◆ (ᵘs̲wim k − 1) c̲at f
}
slow ← ᵘs̲wim 31
s̲hape slow

⍝ typed:
⍝   u:f_rame := { k -> ((n_eg k) o_-_2 pond) c_at k o_- water }
⍝   u:s_wim := { k ->
⍝     f := u:f_rame k
⍝     k = 0 ? (1 c_at s_hape f) r_eshape f; (u:s_wim k - 1) c_at f
⍝   }
⍝   slow := u:s_wim 31
⍝   s_hape slow
32 4 32

It works, but it gives up the array way: the frames are no longer one rotate, and the recursion is the loop the rotate had removed.

The fix: catenate along axis 2

c_at_k joins along axis k. It moves axis k of both arguments to the front, joins, and moves it back. The axes other than k must match, and an argument of one rank less is a single cell, so the 32 by 32 waves are one row under each of the 32 frames.

swim ← frames c̲at₂ waves
s̲hape swim
7 s̲elect swim

⍝ typed:
⍝   swim := frames c_at_2 waves
⍝   s_hape swim
⍝   7 s_elect swim
32 4 32
                       _        
        _    _      __(.)<      
       (.)< (.)<    \___)       
~   ~  ~~ ~   ~  ~~ ~   ~ ~  ~~ 

The two ways agree.

swim m̲atch slow

⍝ typed:
⍝   swim m_atch slow
1
shown ← ⎕S̲HOW ⎕G̲RID swim

⍝ typed:
⍝   shown := []S_HOW []G_RID swim

literate-duck-waves.svg

The two side by side

Workaround With c_at_2
a function per frame, a recursion two rotates and one join
frames built one at a time every frame at once, as arrays
works on any version of the language needs catenate along an axis

The workaround is still worth knowing: when a picture is too large to hold every frame at once, building one frame at a time is the way to go.

Literate documents · Live demo · Repository