ASCII animated snow scene

Perl, 196 / 239 chars

chomp(@p=(@f=($"x80)x24,<>)[-24..-1]);{@s=(join("",map rand>.1?$":"*",1..80),@s);if(@s>23){$t=$f[$_],print$_?$/:"\e[H",($f[$_]|=$s[$_]&$p[$_])|($s[$_]&=~$t^$f[$_])for 0..23;select"","","",.1}redo}

This solution differs from your JS example in that the pattern is filled from top down rather than from bottom up, but I assume that's OK since you didn't say anything about it in the rules.

A trivial 1-char reduction can be obtained by replacing \e with a literal ESC character, but that makes the code much harder to read and edit.


Update: I did manage to come up with a version that fills the pattern from bottom up, and doesn't allow snow to fall through the filled parts of the pattern, as in the example JS implementation, at the cost of 43 extra chars:

chomp(@p=(@q=@f=($"x80)x24,<>)[-24..-1]);{@s=(join("",map rand>.1?$":"*",1..80),@s);if(@s>23){my$q;$q[-1-$_]=($q|=$p[-$_]&~$f[-$_])for@a=0..23;print$_?$/:"\e[H",($f[$_]|=$s[$_]&$p[$_]&~$q[$_])|($s[$_]&=~$f[$_])for@a;select"","","",.1}redo}

Replacing ($s[$_]&=~$f[$_]) with just $s[$_] would save 11 chars by letting falling snow pass through the filled parts of the pattern (which matches the spec, but not the example implementation).


OK, since I still seem to be leading the race after a week, I guess I should explain how my solution works to encourage more competition. (Note: This explanation is for the 196-char top-down filling version. I may amend it to include the other version later.)

First of all, the one big trick my solution is based on is that, due to the way ASCII character codes are arranged, the 1-bits in the ASCII code for a space just happen to be a subset of those in the code for an asterisk.

Thus, the following expressions are true: " " & "*" eq " " and " " | "*" eq "*". This is what lets me use bitwise string operations for combining the static and moving parts of the scene without having to loop over individual characters.

So, with that out of the way, let's go over the code. Here's a de-golfed version of it:

chomp(@p = (@f = ($" x 80) x 24, <ARGV>)[-24..-1]);
{
    @s = (join('', map((rand > 0.1 ? $" : '*'), 1..80)), @s);
    if (@s > 23) {
        foreach (0 .. 23) {
            $t = $f[$_];
            print( $_ ? $/ : "\e[H" );
            print( ($f[$_] |= $s[$_] & $p[$_]) | ($s[$_] &= ~$t ^ $f[$_]) );
        }
        select '', '', '', 0.1;
    }
    redo;
}

The first line sets up the arrays @f (for "fixed") and @p (for "pattern"). @f will form the fixed part of the display, and starts out containing nothing but spaces, while @p, which is not shown directly, contains the input pattern; as the animation proceeds, we'll add more and more asterisks to @f until it eventually looks just like @p.

Specifically, @f = ($" x 80) x 23 sets @f to 24 strings of 80 spaces each. ($" is a special Perl variable whose default value just happens to be a space.) We then take this list, append the input lines to it using the readline operator <>, take the last 24 lines of this combined list and assign it to @p: this is a compact way to pad @p with blank lines so that the pattern appears where it should. Finally, we chomp the input lines in @p to remove any trailing newlines so that they won't cause problems later.

Now, let's look at the main loop. It turns out that {...;redo} is a shorter way to write an infinite loop than while(1){...} or even for(;;){...}, especially if we get to omit the semicolon before redo because it immediately follows an if block.

The first line of the main loop introduces the array @s (for "snow", of course), to which it prepends a random 80-character string of 90% spaces and 10% asterisks on every iteration. (To save a few chars, I never actually pop extra lines off the end of the @s array, so it keeps getting longer and longer. Eventually that will grind the program to a halt as the array gets too long to fit in memory, but that will take much longer than most people would ever watch this animation for. Adding a pop@s; statement before the select would fix that at the cost of seven chars.)

The rest of the main loop is wrapped in an if block, so that it only runs once the @s array contains at least 24 lines. This is a simple way to comply with the spec, which requires the whole display to be filled with falling snow from the start, and also simplifies the bitwise operations a bit.

Next comes a foreach loop, which in the golfed version is actually a single statement with a for 0..23 modifier. Since the content of the loop probably needs some explanation, I'm going to unpack it a bit more below:

foreach (0 .. 23) {
    print $_ ? $/ : "\e[H";     # move cursor top left before first line, else print newline
    $t = $f[$_];                # save the previous fixed snowflakes
    $f[$_] |= $s[$_] & $p[$_];  # snowflakes that hit the pattern become fixed 
    $s[$_] &= ~$t ^ $f[$_];     # ...and are removed from the moving part
    print $f[$_] | $s[$_];      # print both moving and fixed snowflakes ORed together
}

First of all, $_ is the default loop counter variable in Perl unless another variable is specified. Here it runs from 0 to 23, i.e. over the 24 lines in the display frame. $foo[$_] denotes the element indexed by $_ in the array @foo.

On the first line of the de-golfed loop, we print either a newline (conveniently obtained from the $/ special variable) or, when $_ equals 0, the string "\e[H", where \e denotes an ESC character. This is an ANSI terminal control code that moves the cursor to the top left corner of the screen. Technically, we could omit that if we assumed a specific screen size, but I kept it in this version since it means I don't have to resize my terminal to run the animation.

On the $t = $f[$_] line, we just save the current value of $f[$_] in a "temporary" variable (hence $t) before potentially changing it in the next line, where $s[$_] & $p[$_] gives the intersection (bitwise AND) of the falling snow and the input pattern, and the |= operator ORs that into the fixed output line $f[$_].

On the line below that, $t ^ $f[$_] gives the bitwise XOR of the previous and current values of $f[$_], i.e. a list of the bits we changed in the previous line, if any, and negating either of the input strings with ~ negates the output. Thus, what we get is a bitmask with all bits set to 1 except those that we just added to $f[$_] on the previous line. ANDing that bitmask onto $s[$_] removes those bits from it; in effect, this means that when a falling snowflake fills in a hole in the fixed pattern, it gets removed from the falling snow array.

Finally, print $f[$_] | $s[$_] (which in the golfed version is implemented by just ORing the two previous lines together) just prints the union (bitwise OR) of the fixed and moving snowflakes on the current line.

One more thing left to explain is the select '', '', '', 0.1 below the inner loop. This is just a klugy way to sleep 0.1 seconds in Perl; for some silly historical reason, the standard Perl sleep command has one-second resolution, and importing a better sleep from the Time::HiRes module takes more chars than abusing 4-arg select.


HTML and JavaScript, 436 chars

Prepend it to the input:

<body onload="for(a=[],b=[],c=document.body.firstChild,e=c[H='innerHTML'].split(N='\n'),f=e.length-1,g=24,h=g-f;f--;)for(X=80;X--;)b[80*(h+f)+X]='*'==e[f][X];for(setInterval(F='for(y=24;y--;)for(x=80;x--;)if(a[w=80*y+x]){d=1;if(b[w])for(d=0,z=y+1;24>z;++z)b[s=80*z+x]&&!a[s]&&(d=1);d&&(a[w]=0,a[w+80]=1)}for(x=80;x--;).1>Math.random(i=0)&&(a[x]=1);for(t=\'\';1920>i;++i)t+=\'* \'[+!a[i]],79==i%80&&(t+=N);c[H]=t',67);g--;)eval(F)"><pre>

See it run for each example: code golf, Stack Overflow logo, Christmas trees. Internet Explorer users need to run version 9 and set the "Document Mode" to "IE9 standards" (using the F12 developer tools) for this submission to correctly work.