Animate Jacob's ASCII Ladder

23

4

You may have seen Jacob's Ladder in children's science museums. If you're not familiar with what they look like, there are several images and video examples on the Wikimedia Commons. The challenge today is to create an animated ASCII version of the electrical gadget. In the end, it should look something like this:

LadderGIFExample


Ladder Construction

Here is the basic shape of a ladder with a height (H) of 6:

6   \            /
5    \          /
4     \        /
3      \      /
2       \    /
1        \  /
0         ¯¯

The numbers on the left simply indicate row number for this example and should not be included in the output. We'll refer to a given row by it's number (R). Row 0 is the bottom ¯¯. Each row 1 through H is comprised of four parts:

  • A space (U+0020) repeated (H - R) times
  • A back slash \ (U+005C)
  • A space (U+0020) repeated (2 * R) times
  • A forward slash / (U+002F)

Row 0 is identical except both the slashes are replaced with a macron ¯ (U+00AF). Trailing whitespace at the end of each line or below the ladder is OK. Leading whitespace is not.


Arc Construction

Once the ladder is constructed, you can create arcs between the left and right side. One arc is entirely within a row and replaces the spaces between the leading \ and trailing /. Therefore, row 2 will have 4 characters in its arc, row 3 will have 6, and so forth. Each arc is composed using the following rules:

  • The only allowable characters are _/¯\ (U+005F, U+002F, U+00AF, U+005C)
  • In order to ensure a smooth appearance, any ¯ or / must be followed by a ¯ or \
  • In order to ensure a smooth appearance, any _ or \ must be followed by a _ or /
  • The two rules above apply to the edges of the ladder as well
  • The three rules above effectively mean that the first character in the arc must be _ or / and the last character must be _ or \ (\¯\_// is invalid on both ends but \_/¯\/ is OK)
  • There must be a non-zero chance for each allowable character to occur at a given point
  • Each arc is independent from every other arc

Animation

The life of a single arc is created by starting it at row 1 and "moving" it up one row at a time until it reaches the top. I.E., first generate an arc at row 1, then set it back to spaces and generate an arc at row 2, and so forth. Given a number of arcs to show (N), show the complete life of that many arcs one at a time using the following guidelines:

  • Only one arc should be "alive" at any one time. The next arc cannot start until the current one reaches the top and then extinguishes.
  • Each row of the arc's life should be shown for exactly one frame
  • There should one frame of just the basic ladder (with no arcs) before a new arc starts (optional before the first arc)
  • The animation should show the full life of N arcs. If N=0, it should animate random arcs forever until stopped.
  • If N>0, you may still loop the animation forever but it must be a loop of the same arcs over and over. (The example GIF at the top of this post has H=6 and N=3 but it loops forever.)
  • The animation should occur in-place. That is, each frame should completely overwrite the next frame and be in the same location.
  • The length of each frame can be whatever you want but make it watchable by a human (I.E., use your common sense: 0.01s/frame and 30s/frame are both unacceptable.)

Input / Output

  • Input and Output can be in any standard format
  • You can export a GIF, write text to the screen, output a single file for each frame, or any other reasonable means
  • Standard loopholes are forbidden
  • The height of the ladder H will be a positive integer
  • The number of arcs to show N will be a non-negative integer
  • Both H and N are taken as inputs in any order you choose (Please include the order in your answer)

Winning Condition

This is so the shortest code wins.

Sandbox

Engineer Toast

Posted 2018-06-04T12:48:13.803

Reputation: 5 769

1Can arc be generated symmetrical by its center? I can't see restriction to that in rules – Dead Possum – 2018-06-04T13:08:34.573

Can i print each frame after each other to the console? – TFeld – 2018-06-04T13:31:22.500

@DeadPossum I was thinking you were right despite the fact it wouldn't look very lightning-y but it's actually disallowed by the combination of two rules: the first character in the arc must be _ or / and the last character must be _ or \ and There must be a non-zero chance for each allowable character to occur at a given point. In order to be symmetrical, both the first and last character would have to be _ every single time which means there is zero chance of either / or \\ occurring. – Engineer Toast – 2018-06-04T13:40:16.137

@TFeld So long as each frame appears in the same location on the screen, yes. That means you'll have to clear the console (or maybe scroll down, if that's possible) each time. – Engineer Toast – 2018-06-04T13:46:37.223

@EngineerToast I meant lightning like /\/\/\ is symmetrical by center. Visually, not character-wise – Dead Possum – 2018-06-04T14:20:45.207

@DeadPossum There's nothing wrong with that segment by itself, no. You have to encapsulate it in the ladder, though, so that last forward slash can't be the last character of the arc. (Side note: formatting slashes as code in comments is tricky.)

– Engineer Toast – 2018-06-04T15:57:47.777

2

Does the macron requirement mean that QBasic can't compete? It uses CP437, in which code point 0xAF is ».

– DLosc – 2018-06-04T17:22:16.780

@DLosc I hate to exclude language but, sadly, the macron is needed to make the arc appear consistent. I don't see anything in that code page that fits the bill, but I'll edit it in if you can find something that aligns with the top of the slashes. – Engineer Toast – 2018-06-04T19:01:42.033

Answers

5

Python 2, 287 271 270 276 275 bytes

import time,random
r,n=input()
c=n*-~r or-r
while c:
 c-=1;L=[list(' '*i+'\\'+'  '*(r-i)+'/')for i in range(r)];x=c%-~r;time.sleep(1);y=x+1;exec"L[x][y]=random.choice('\xaf/\_'[L[x][y-1]in'\_'::2][y==2*r-x:]);y+=1;"*2*(r-x)
 for l in['']*99+L+[' '*r+'\xaf'*2]:print''.join(l)

Try it online!

Does not clear the screen on tio, but works in a console.

Gif of it running:

enter image description here

TFeld

Posted 2018-06-04T12:48:13.803

Reputation: 19 246

A bit sneaky but you could use print'\n'*99 instead of os.system('cls') and lose the os import. Still doesn't work on TIO but works in both Windows and Linux consoles. – ElPedro – 2018-06-04T14:32:18.827

1There should [be] one frame of just the basic ladder (with no arcs) before a new arc starts (optional before the first arc) – wastl – 2018-06-04T15:49:44.143

5I think you're using hyphens (U+002D) instead of macrons (U+00AF). I don't think it'll increase your byte count to fix it. Also, as @wastl pointed out, there's no empty ladder frame between arcs. – Engineer Toast – 2018-06-04T16:08:45.627

The bottom row uses macrons but the arcs do not – Engineer Toast – 2018-06-06T12:34:38.520

1@EngineerToast Fixed now :) – TFeld – 2018-06-06T12:50:20.537

The length of each frame can be whatever you want but make it watchable by a human allows you to choose a frame time of one second, thus not needing a byte for the decimal point. – Jonathan Frech – 2018-06-06T22:20:42.433

It's 2018 and people use PowerShell unironically? You should try jQuery. – Stan Strum – 2018-06-08T17:02:00.077

4

JavaScript (ES6), 245 bytes

f=(o,h,n,i=0)=>(o.innerText=[...Array(h+1)].map((_,j)=>` `.repeat(j)+(j<h?`\\${[...Array(w--*2)].map((_,k)=>h+~j-i?` `:k>w*2|Math.random()<.5?s[s=t,1]:s[s=`¯\\`,0],s=t=`/_`).join``}/`:`¯¯`),w=h).join`
`,(++i<h||--n)&&setTimeout(f,250,o,h,n,i%h))
Height: <input type=number min=1 value=6 id=h><br>Arcs: <input type=number min=0 value=3 id=n><br><input type=button value=Go! onclick=f(o,+h.value,+n.value)><pre id=o></pre>

Byte count assumes ISO-8859-1 encoding.

Neil

Posted 2018-06-04T12:48:13.803

Reputation: 95 035

It's possible to reduce it to 242 by defining A=x=>[...Array(x)].map; at the begining and replacing both usages. – Bary12 – 2018-06-06T09:17:47.237

@Bary12 You can't return map, that's just a property of Array.prototype and no use on its own. I tried working versions but they all came out longer than 245 bytes. – Neil – 2018-06-06T09:26:17.263

3

C (gcc), 406 bytes

#define p(X) printf(X),usleep(999)
#define x(X) do{s[X]=0;p(s);s[X]=' ';}while(0)
char c[2][2]={95,47,92,'¯'};R;i;j;k;a(){char s[2]={92,0};for(j=0;j<2*R-1;++j,p(s))*s=c[*s<50][rand()%2];*s=c[*s<50][0];p(s);}f(H,N){char s[99];for(i=0;i<99;++i)s[i]=' ';p("\e[s");for(i=0;;++i){i%=(N?N:i+1);srand(i^H^N);for(k=1;k<H;++k){for(R=H;--R;){x(H-R+1);p("\\");if(R==k)a();else x(2*R);p("/\n");}x(H);p(" ¯¯\n\e[u");}}}

Try it online!

Description:

#define p(X) printf(X),usleep(999)              // Define p to printf(p) + delay
#define x(X) do{s[X]=0;p(s);s[X]=' ';}while(0)  // Define x(X) to print X spaces
                                                // This uses a string s full of
                                                // spaces and adds the null
                                                // terminator where approrpiate
char c[2][2]={95,47,92,'¯'};                    // 2d array of 'next arc' options
R;i;j;k;                                        // Variables
a(){                                            // a() -> print arc for row R
    char s[2]={92,0};                           // s is a string of next char
                                                // initialize to backslash
    for(j=0;j<2*R-1;++j                         // loop over each character
            ,p(s))                              // printing s each time
        *s=c[*s<50][rand()%2];                  // set s to the next arc char
    *s=c[*s<50][0];                             // set s to the 'first' arc char
                                                // note that in definition of c
                                                // first means appropriate as
                                                // final character before /
    p(s);}                                      // print the last character
f(H,N){                                         // f(H,N) -> print jacob ladder
    char s[99];for(i=0;i<99;++i)s[i]=' ';       // this is the space string for x
    p("\e[s");                                  // ANSI terminal save position
    for(i=0;;++i){i%=(N?N:i+1);                 // loop i->N (or i->INT_MAX if N=0)
        srand(i^H^N);                           // seed random with i XOR H XOR N
        for(k=1;k<H;++k){                       // for each row (bottom to top)
            for(R=H;--R;){                      // for each row (top to bottom)
                x(H-R+1);p("\\");               // print left "    \"
                if(R==k)                        // if on the arc row
                    a();                        // print the arc
                else x(2*R);                    // otherwise print spaces
                p("/\n");}                      // finish off the row
            x(H);p(" ¯¯\n\e[u");}}}             // print bottom line and move back

LambdaBeta

Posted 2018-06-04T12:48:13.803

Reputation: 2 499

Note: only really works in Xterm... many terminal emulators just don't support save/restore position. – LambdaBeta – 2018-06-04T16:04:51.100

The bottom row is row 0 and has two macrons only. It is not \--/. That's probably an easy fix. Are you able to capture and post a GIF of it working in Xterm? – Engineer Toast – 2018-06-04T16:11:14.563

Sadly I lack the tools to do that (just playing around during build times at work). I will update with correct row 0 though, it is an easy fix. – LambdaBeta – 2018-06-04T16:13:04.850

Invalid: There should [be] one frame of just the basic ladder (with no arcs) before a new arc starts (optional before the first arc) – wastl – 2018-06-04T17:41:37.813

changing k=1 to k=0 fixes that... 0 byte cost. Will update soon. – LambdaBeta – 2018-06-04T17:44:42.383

You don't need a space between the macro and the definition. – Zacharý – 2018-11-16T17:37:19.043

2

PowerShell, 347 319 bytes

filter c{Param($h,$n)if($n-eq0){$n=-1}for($x=0;$x++-ne$n;){($h..1)|%{$l=(($h..1)|%{"$(" "*($h-$_))\$(" "*$_*2)/"})+"$(" "*$h)¯¯"
$r="Get-Random"
$i=0
$z=-join((0..(($h-$_)*2))|%{$i=switch($i%3){0{&$r 0,1}default{&$r 2,3}}"_/¯\"[$i]})+"_\\_"[$i]
$l[$_-1]=($l[$_-1]).Substring(0,$_)+"$z/"
cls
$l
Sleep -m 250}}}

Try it online! Couldn't get $args to play nice, so the link calls the function without clearing the console.

Ungolfed

filter c{
    Param($h,$n)
    if($n -eq 0){$n=-1} # inelegant swap to allow for an infinite loop. 
                        # Curse you zero-indexing!
    for($x=0;$x++-ne$n;)
    {
        ($h..1) | % {         
            $l=(($h..1)|%{ # (( double paren is needed to induce each line 
                           # as a new array element
                "$(" "*($h-$_))\$(" "*$_*2)/" # offset by total height. 
                                              # N spaces + rung + N*2 spaces + rung
            })+"$(" "*$h)¯¯" # last line is the floor of the ladder

            $r="Get-Random" # shorter to declare once and execute with & operator

            $i=0 # initialize $i so we choose only _ or / for the first char

            $z=-join( # build an electric ZAP!
                (0..(($h-$_)*2))|%{                    
                    $i = switch($i%3) { # choose next char based on previous selection
                        0{&$r 0,1}
                        default{&$r 2,3}
                    }    
                    "_/¯\"[$i]
                }
            )+"_\\_"[$i] # final char is \ or _ to rejoin the ladder        
            $l[$_-1]=($l[$_-1]).Substring(0,$_)+"$z/" # select one rung of the ladder 
                                                      # append an electric ZAP!                
            cls # clear the console
            $l  # display the ladder
            Sleep -m 250
        }
    }
}

Peter Vandivier

Posted 2018-06-04T12:48:13.803

Reputation: 211

It's a small thing, but the bottom row is hyphens instead of macrons. It's a zero-byte change $l=(($h..1)|%{"$(" "*($h-$_))\$(" "*$_*2)/"})+"$(" "*$h)¯¯" – Engineer Toast – 2018-06-06T12:36:05.207

1¯\(°_o)/¯ oops! swapped in macrons :p – Peter Vandivier – 2018-06-06T13:10:32.573

1

I don't know PowerShell too well, but you can remove most of the new-lines. In addition for($x=0;$x-ne$n;$x++) can be for($x=0;$x++-ne$n;). I got it down to 324 bytes (321 chars) doing so. Tips for golfing in <all languages> and Tips for golfing in PowerShell might be interesting to read through as well.

– Kevin Cruijssen – 2018-06-06T13:35:32.567

1sleep 1 saves a bit (it defaults to -seconds) but is pretty slow but still reasonable-ish, sleep -m 99 is pretty quick but also reasonable. Saves 5/1 bytes depending on what you like. Didn't check Kevin's attempt but function to filter is a free byte as well. – Veskah – 2018-06-06T20:21:06.963

1

Ruby, 293 bytes

m={}
"   __/\\_/¯¯\\/¯\\".chars.each_slice(3){|e|u,*v=e;m[u]=v}
a=->l,c{l<1?"/":(d=m[c].sample;c+a[l-1,d])}
n=gets.to_i
h=gets.to_i
o=0
while o<n||n<1
h.times{|i|puts (0...h).map{|j|" "*j+"\\"+a[2*(h-j),i==h-j-1?["_","/"].sample: " "]}*"\n";puts" "*h+"¯¯";sleep(0.3);puts"\n"*99}
o+=1
end

Try it online!

I'm on windows so it just prints many "\n" to clear the console. Takes 2 arguments n and h as two lines on stdin.

crashoz

Posted 2018-06-04T12:48:13.803

Reputation: 611