22
\$\begingroup\$

You live inside a terminal that is 80 characters wide. You are bored, so you decide to play dominoes. No, not the boring kind that look like Scrabble, the fun kind where you spend an hour setting them to watch them fall in a second.

In terminals, dominoes look like this:

|   upright domino
\   left-tilted domino
/   right-tilted domino
__  fallen domino

As we all know, if a tilted domino touches an upright one, the second domino gets tilted as well. The only exception to this is if two tilted dominoes touch it:

|\ --> \\        /| --> //        /|\ --> /|\

Adjust your terminal's gravitational constant so this transition takes 100 ms.

If a tilted domino is supported by another domino or the terminal's walls, its journey ends.

None of the tilted dominoes in

\||||____||||/__                /|\    /\    /|\                __\||||____||||/

(80 characters) will move, since the two outmost tilted dominoes are supported by the terminal's walls and all others are supported by other dominoes.

However, if the space in the tilting direction is empty, the domino falls down:

| \\ --> |__\        // | --> /__|

Terminal. Gravitational constant. You get the point…

Finally, there's a slight wind from the left, so right-tilted dominoes fall faster than left-tilted ones:

|/ \| --> |__\|

Task

Write a program/function that shows an animation of playing dominoes in a terminal.

Your code should do the following:

  1. Read a string from input, representing the initial state of the dominoes.

    This string will contain no more than 80 characters and consist solely of the dominoes described above and empty spaces.

  2. Print the state and wait for 100 ms.

  3. Transform the state as explained above.

  4. If the state changed, go back to 2.

Additional rules

  • The length of the input string does not affect the terminal's width; even if the string is shorter than 80 characters, the walls of the terminal are still 80 characters apart.

  • Each time step 2 is executed, the state should get printed to the same location, overwriting the previous state.

  • Since some languages are incapable of waiting exactly 100 ms, feel free to wait any amount between 50 and 1000 ms.

  • Standard rules apply.

Examples

  • For the initial state

     ||\/||
    

    print the following (one over the other):

     ||\/||
     |\\//|
     \\\///
    __\\//__
    
  • For the initial state

    /||||\
    

    print the following

    /||||\
    //||\\
    ///\\\
    
  • For the initial state

    /|||\
    

    print the following

    /|||\
    //|\\
    
  • For the initial state

    |/ \|/ \|/ \|/ \|
    

    print the following:

    |__\|__\|__\|__\|
    
  • For the initial state (80 characters)

    \||||____||||/__                /|\    /\    /|\                __\||||____||||/
    

    print the following

    \||||____||||/__                /|\    /\    /|\                __\||||____||||/
    
\$\endgroup\$
13
\$\begingroup\$

Retina, 87 86 85 bytes

Thanks to Dennis for saving 1 byte.

^.{0,79}$
$0 
:`^
<ESC>c
(`/ | \\
__
/\|(?!\\)
//a
(?<!/)\|\\
\\
$
aaaaa
a
aaaa
(a+)+b|a
<empty>

<ESC> should be replaced by the actual control character (0x1B). <empty> represents an empty trailing line. You can then run the above code from a single file with the -s flag.

The code requires a terminal which supports ANSI escape codes. I can't suppress the line feed in Retina's output so I need to clear the entire console with <ESC>c each time. I've tested the code in bash using Mono to run Retina.

Explanation

^.{0,79}$
$0 

We start by appending a space if the input contains fewer than 80 characters. This is so that a / at the right end does not have to be treated separately.

:`^
<ESC>c

Now we prepend <ESC>c to the string, which is the ANSI escape code for clearing the terminal. So every time the string is printed it will do so at the top of the terminal. The :` instructs Retina to print out the result of this substitution, i.e. the initial configuration.

(`/ | \\
__

(` begins a loop. Since there is no matching ), the loop is assumed to go until the last stage of the program. Each iteration will simulate one step of the dominoes falling and then "sleep" a bit. This first stage replaces / and \ next to a space into __. This automatically handles the / \ case correctly, because matches cannot overlap and are searched for from left to right. So the /<sp> would be matched and turned into __ such that the \ can't be matched, and we get the correct __\.

/\|(?!\\)
//a

This turns /| into // provided there's no \ next to it. We append an a such that this new / doesn't mess with the next stage (which shouldn't "know" about this change yet).

(?<!/)\|\\
\\

The opposite situation: turn |\ into \\ provided there's no / next to it. We don't need to put an a here, because we're done with this step of the simulation.

Now the sleeping part...

$
aaaaa

Appends 5 more as to the end of the code.

a
aaaa

Turns each a into 4 as, so we get 20 as at the end.

(a+)+b|a
<empty>

Now the fun part... we sleep for a bit with the help of catastrophic backtracking. There is an exponential amount of ways to split up a match of (a+)+ between repetitions of the group. Because the b causes the match to fail, the engine will backtrack and try every single one of those combinations before deciding that (a+)+b can't match. For the twenty as at the end that takes something like half a second.

At the same time, we allow the regex to match a single a, but only after doing the backtracking. When that matches, we replace it with an empty string, removing all the as we inserted for one reason or another from the string.

That leaves printing the string at the end of the loop iteration. Here it comes in handy that I haven't fixed the printing behaviour of loops in Retina yet. Currently, there is only one flag for each stage, which says "print" or "don't print". The default is "don't print" except for the last stage in the program, which defaults to "print". But the stage is looped, so that means it actually prints the current string on each iteration. Normally, that is really annoying, and you almost always need to include an additional empty stage at the end if you only want the final result, but here it lets me save four bytes.

\$\endgroup\$
6
\$\begingroup\$

Javascript (ES6), 206 148 129 158 bytes

I'd finally gotten it down to a nicely low point, but it wouldn't clear the console or append an extra space; these problems have been fixed now.

c=console;d=s=>{c.clear(s[79]||(s+=' ')),c.log(s),t=s[R='replace'](/\/ | \\/g,'__')[R](/\/\|/g,'//a')[R](/\|\\/g,'\\\\')[R](/a/g,'');t!=s&&setTimeout(d,99,t)}

Alternate 153 byte version which should work in Node.JS:

d=s=>{s[79]||(s+=' '),console.log("\033c"+s),t=s[R='replace'](/\/ | \\/g,'__')[R](/\/\|/g,'//a')[R](/\|\\/g,'\\\\')[R](/a/g,'');t!=s&&setTimeout(d,99,t)}

IMHO, it's quite fun to play with. Try an HTML version here:

output=document.getElementById("output"),console.log=a=>output.innerHTML=a;
d=s=>{s[79]||(s+=' ');console.log(s),t=s[R='replace'](/\/ | \\/g,'__')[R](/\/\|/g,'//a')[R](/\|\\/g,'\\\\')[R](/a/g,'');t!=s&&setTimeout(d,99,t)}
s=n=>{n=n||15,r='';for(i=0;i<n;i++)r+=(i%2?'|'.repeat(Math.random()*16|0):'\\ /'[Math.random()*3|0].repeat(Math.random()*3+1|0));return r} // randomizer
input=document.getElementById("input");
input.addEventListener("keydown",e=>e.keyCode==13?(e.preventDefault(),d(input.value)):0);
<p>Enter your dominoes here:</p>
<textarea id="input" rows="1" cols="80">|||\ |||\ /|\ /||||||||\ /|||| /|||</textarea>
<button id="random" onclick="input.value=s()">Randomize</button><button id="run" onclick="d(input.value)">Run</button>
<p>Result:</p>
<pre id="output"></pre>

There's probably a good bit more room for golfing. Suggestions welcome!

\$\endgroup\$
  • \$\begingroup\$ +1 for the runnable demo that wasted 10 minute of my time, and +1 for the randomizer function. However, as Dennis mentions, it fails for the first test case. Try / or /| and you will see the tile does not fall all the way as it should. \$\endgroup\$ – dberm22 Sep 18 '15 at 11:51
  • \$\begingroup\$ @Dennis Thanks for pointing out these problems. I believe I've fixed both of them now. \$\endgroup\$ – ETHproductions Sep 18 '15 at 14:17
  • \$\begingroup\$ Node isn't happy about the fat arrow, but it works fine otherwise. You can replace \033 with a literal ESC byte, saving 3 bytes. \$\endgroup\$ – Dennis Sep 18 '15 at 14:38
2
\$\begingroup\$

Perl 5, 154 146

Had to use a temporary character to maintain the state between 2 regexes.
To deal with the risk that something like / | | | \ would end up as / / / \ \ instead of / / | \ \ .

$_=substr(pop.' ',0,80);$|++;while($}ne$_){print"$_\r";$}=$_;s@ \\|/ @__@g;s@/\|(?=[^\\])@/F@g;s@([^/])\|\\@$1\\\\@g;tr@F@/@;select($\,$\,$\,0.1)}

Test

$ perl dominos.pl '|\ |\/|||\/|'
|\__\//|\\/__
\$\endgroup\$
  • 1
    \$\begingroup\$ You can eliminate several backslashes if you use a delimiter other than slash — e.g. s, \\|/ ,__,g instead of s/ \\|\/ /__/g. \$\endgroup\$ – hobbs Sep 18 '15 at 2:08
  • \$\begingroup\$ Good tip. Forgot about that trick. And a few bytes extra were cut by using negated sets. \$\endgroup\$ – LukStorms Sep 18 '15 at 9:51
2
\$\begingroup\$

ES6, 220 218 195 bytes

Minified

f=d=>{var e,c=console;if(!d[79])d+=' ';c.clear();c.log(d);e=d;d=d[R='replace'](/\/\|\\/g,'a')[R](/\/ | \\/g,'__')[R](/\/\|/g,'//')[R](/\|\\/g,'\\\\')[R]('a','/|\\');if(e!=d)setTimeout(f,100,d);};

More Readable

f=d=> {
    var e,
    c=console;
    if(!d[79])
        d+=' ';
    c.clear();
    c.log(d);
    e=d;
    d = d[R='replace'](/\/\|\\/g, 'a')  //Substitute '/|\' with 'a' so it doesn't get replaced
        [R](/\/ |  \\/g, '__')     //Replace '/ ' and ' \' with '__'
        [R](/\/\|/g, '//')    //Replace '/|' with '//'
        [R](/\|\\/g, '\\\\')  //Replace '|\' with '\\'
        [R]('a', '/|\\');     //Put '/|\' back
    if(e!=d)
        setTimeout(f,100,d);
};
\$\endgroup\$
  • 2
    \$\begingroup\$ Welcome to Programming Puzzles & Code Golf! 1. I'm not sure why you are using the ES6 notation. () = > { and }() can simply be removed from your code. 2. I don't think alert boxes are an acceptable output format for an animation. You can either embed your JS in HTML or make the required change so it would work from the command line. 3. In either case, your code must wait approx. 100 ms between printing one state and the next. \$\endgroup\$ – Dennis Sep 17 '15 at 23:24
  • 2
    \$\begingroup\$ Welcome to PPCG! I would suggest checking out this post and this post to help improve your golfing. \$\endgroup\$ – jrich Sep 17 '15 at 23:29
  • \$\begingroup\$ Thank you for the suggestions and links to golfing tips. It is still a little long, but I shortened it a bit and added the timer. I will go through the tips more in depth and see what I can shorten. \$\endgroup\$ – user3000806 Sep 18 '15 at 6:42
  • 1
    \$\begingroup\$ That should work in a terminal now, but it still won't print the updated state over the old one. On Linux, you can fix this by calling console.log("^[c"+d) instead, where ^[ is the ESC character (one byte). \$\endgroup\$ – Dennis Sep 18 '15 at 7:02
  • 1
    \$\begingroup\$ If you change the first .replace to [R='replace'], then each subsequent one to [R], this will cut down quite a bit. You could also save a few bytes by using setTimeout(f,100,d) in place of the current setup. \$\endgroup\$ – ETHproductions Sep 18 '15 at 13:56
2
\$\begingroup\$

C#, 335 bytes

Not a great choice of language.

I abused the delay being allowed between 50 and 1000 to select a two-digit number.

New lines and indentation added for clarity:

namespace System.Threading{
    class P{
        static void Main(string[]z){
            var c=@"/|\,/|\,/|,//,|\,\\,/ ,__, \,__".Split(',');
            for(string a=z[0].PadRight(80),b="";a!=b;){
                Console.Clear();
                Console.Write(b=a);
                Thread.Sleep(99);
                a="";
                for(int i,j;(i=a.Length)<80;)
                    a+=(j=Array.FindIndex(c,d=>b.Substring(i).StartsWith(d)))%2==0
                        ?c[j+1]
                        :b.Substring(i,1);
            }
        }
    }
}
\$\endgroup\$
1
\$\begingroup\$

PHP, 175 bytes

$i=sprintf("%-80s",$argv[1]);$p='preg_replace';do{echo($o=$i)."\r";$i=$p('(/\|\\\\(*SKIP)(?!)|(?|(/)\||\|(\\\\)))','$1$1',$p('(/ | \\\\)','__',$i));usleep(1e5);}while($i!=$o);

Un-minified:

$input = sprintf("%-80s",$argv[1]);
do {
  echo $input."\r";
  $old = $input;
  $input = preg_replace('(/ | \\\\)','__',$input);
  $input = preg_replace('(/\|\\\\(*SKIP)(?!)|(?|(/)\||\|(\\\\)))','$1$1',$input);
  usleep(100000);
}
while( $input != $old);

Basically regex golf. First flattens any falling dominoes that have space (and due to left-to-right matching order, the "wind" blows). Then comes the ugly part (curse you slashes!)

  • Match /|\, then skip it.
  • Match (/)| and replace with //
  • Match |(\) and replace with \\

This makes the dominoes fall. Finally, just wait 100ms for the next step.

Using () as delimiters on the regex means the /s don't need escaping, which helps minimally!

\$\endgroup\$
  • \$\begingroup\$ You're allowed to wait 50ms instead of 100, saving 1 char ;) Does PHP allow 10^5 ? \$\endgroup\$ – BlueCacti Sep 18 '15 at 10:03
1
\$\begingroup\$

POSIX shell+sed, 144

sed 's/^.\{1,79\}$/& /;s/.*/printf '"'&\\r'"';sleep .1/;h;:;s,/|\\,/:\\,g;s,\(/ \| \\\),__,g;s,/|,//,g;s,|\\,\\\\,g;H;t;x;y/:/|/;s/\\/\\\\/g'|sh

This is in two parts. The main work of toppling the dominoes is standard sed pattern replacement, accumulating lines into hold space. We temporarily turn /|\ into /:\ to protect it, recovering at the end.

s/^.\{0,79\}$/& /
h

:
s,/|\\,/:\\,g
s,\(/ \| \\\),__,g
s,/|,//,g
s,|\\,\\\\,g
H
t

x
y/:/|/

Since sed hasn't got any way of inserting delays (I looked into terminfo/termcap, but couldn't find any standard way), I wrap each line in printf "...\r"; sleep .1 to print a line every 100ms. I actually do this first, when we have just one line, as the characters in the command won't be touched by any of the substitutions of toppling.

All tested using dash and GNU coreutils, with POSIXLY_CORRECT set in the environment.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.