Predict the Falling Rocks
Perl 5: 98
98 including 2 command line flags.
#!perl -p0
1while/
/,($x="($`)")=~y!#!.!,s/#(.*
$)/%$1/+s/#$x?%|%$x?#/%$1$2%/s;1while s/#$x\./.$1#/s;y!%!#!
Explanation:
#!perl -p0 #read entire input to $_ and print at the end
/\n/;($x="($`)")=~y!#!.!; #calculate pattern matching space
#between two characters in the same column
#looks like "(......)"
1 while s/#(.*\n$)/%$1/+s/#$x?%|%$x?#/%$1$2%/s;
#flood fill solid rock with %
1 while s/#$x\./.$1#/s; #drop loose rock
y!%!#! #change % back to #
CJam, 180 ... 133 101 ... 94 90 87 bytes
qN/~'#/S*_,):L;]N*_,_,*{:A1$='#={[W1LL~)]Af+{W>},1$f=S&,{ASct}*}*}/N/z{S/{$W%}%'#*}%zN*
There is definitely lots of golfing possible, but I wanted to post it first after getting it to work completely.
Look ma! No scrollbars!
Takes the rocks grid (made up of .
and #
without a trailing newline) from STDIN and prints the output to STDOUT
UPDATE : Using an inefficient but shorter partial flood fill to figure out firm rocks.
UPDATE 2: Changed the algorithm for making the rocks fall down. Much shorter now!
UPDATE 3: Did several small optimizations and in the end I was able to bring down the byte count to half of the original code!
How it works:
qN/~'#/S*_,):L;]N* "Preparations";
qN/~ "Read the input, split by new line and expand the array";
'#/S* "In the last row, replace # by space";
_,):L "Copy the last row and store length + 1 in L";
;]N* "Pop the length, wrap everything in array and join by \n";
_,_,*{ ... }/ "Flood fill";
_, "Copy the array and calculate its length";
_, "Copy the length and calculate [0 - length] array";
* "Repeat the above array, length times";
{ ... }/ "Run the code block on each element of the array";
:A1$='#={ ... }* "Process only #";
:A1$ "Store the number in A and copy the input array to stack";
= "Get Ath index element from input array";
'#={ ... }* "Run the code block if Ath element equals #";
[W1LL~)]Af+{W>},1$f=S&,{ASct}* "Flood fill spaces";
[W1LL~)]Af+ "Get the indexes of the 4 elements on the cross formed by"
"the Ath index";
{W>}, "Filter out the negative values";
1$f= "For each of the index, get the char from input string";
S&, "Check if space is one of the 4 chars from above step";
{ }* "Run the code block if space is present";
ASct "Make the Ath character of input string as space";
N/z{S/{$W%}%'#*}%zN* "Let the rocks fall";
N/z "Split the resultant string by newlines and"
"transpose the matrix";
{ }% "Run the code block for each row (column of original)";
S/{ }% "Split by space and run the code block for each part";
$W% "Sort and reverse. This makes # come down and . to go up";
'#* "Join by 3, effectively replacing back spaces with #";
zN* "Transpose to get back final matrix and join by newline";
For the floodfill, we iterate through the whole grid length(grid) times. In each iteration, we are guaranteed to convert at least 1 #
which is directly touching a space to
(space). Space here represents a firm rock group. Thus at the end of length(grid) iterations, we are guaranteed to have all firm rocks represented by spaces.
Try it online here
JavaScript (ES6) 232
s=>{for(s=[...s+'1'.repeat(r=1+s.search('\n'))];s=s.map((c,p)=>c=='#'&(s[p+1]|s[p-1]|s[p-r]|s[p+r])?f=1:c,f=0),f;);for(;s.map((c,p)=>c=='#'&s[p+r]=='.'&&(s[p]='.',f=s[p+r]=c),f=0),f;);return s.join('').replace(/1/g,rok).slice(0,-r)}
As a function with a string parameter and returning a string.
At first, add a bottom row of '1' to identify the ground line.
The first loop search for the fixed rocks (that are near a '1') and marks them as '1' as well.The search is repeated until no more firm rocks are found.
The second loop move the remaining '#' characters towards the bottom row. Again, this is repeated until no rock can be moved.
At last, replace the '1' with '#' again and cut the bottom row.
Less golfed
s=>{
r = 1+s.search('\n');
s = [...s+'1'.repeat(r)];
for (; s = s.map((c,p) => c=='#' & (s[p+1]|s[p-1]|s[p-r]|s[p+r])?f=1:c,f=0),f; );
for (; s.map((c,p) => c=='#' & s[p+r]=='.'&& (s[p] ='.', s[p+r]=c, f=1),f=0),f; );
return s.join('')
.replace(/1/g,'#')
.slice(0,-r)
}
Test (You can have evidence of what rocks are firm and what have fallen)
F=
s=>{for(s=[...s+'1'.repeat(r=1+s.search('\n'))];s=s.map((c,p)=>c=='#'&(s[p+1]|s[p-1]|s[p-r]|s[p+r])?f=1:c,f=0),f;);for(;s.map((c,p)=>c=='#'&s[p+r]=='.'&&(s[p]='.',f=s[p+r]=c),f=0),f;);return s.join('').replace(/1/g,rok).slice(0,-r)}
var rok // using rok that is 3 chars like '#'
function update() {
rok = C.checked ? '@' : '#';
O.textContent=F(I.textContent)
}
update()
td { padding: 5px }
pre { border: 1px solid #000; margin:0 }
<table><tr><td>Input</td><td>Output</td></tr>
<tr><td><pre id=I>.#####....
.#....####
###.###..#
#.#...##..
.####..#.#
......###.
..#...#..#
..#...#..#</pre></td>
<td><pre id=O></pre>
</td></tr></table>
<input type='checkbox' id=C oninput='update()'>Show firm rocks