#!/usr/bin/perl
# 
# This was a fun quiz. :) I haven't been motivated to generate images for
# output, so this just barfs out some high-quality ASCII art. Still, it's
# kind of fun to watch the frost "grow" across my screen.
# 

use warnings;
use strict;

use constant EMPTY => ' ';
use constant VAPOR => '.';
use constant ICE   => '*';

my $t = 0;
$SIG{ALRM} = sub { print STDERR "TICKS: $t\n"; exit };
alarm $ENV{TIMELIMIT} || 120;

frost(create_volume(@ARGV));

# create a volume of size M x N with vapor density D
# and a single ice particle in the center
sub create_volume {
  my ($m, $n, $d) = (@_, 60, 40, 20);
  die "\$m must be even\n" if $m % 2;
  die "\$n must be even\n" if $n % 2;

  my @vol;
  for (1 .. $m) {
    push @vol, [ map {rand(100) < $d ? VAPOR : EMPTY} (1 .. $n) ];
  }
  $vol[$m/2][$n/2] = ICE;

  return @vol;
}

# run frost simulation until all vapor freezes,
# displaying volume each cycle.
sub frost {
  my @vol = @_;
  my ($m, $n) = (scalar @vol, scalar @{$vol[0]});

  my $cs = ($^O eq 'MSWin32') ? `cls` : `clear`;
  print $cs, text(@vol);
  print "t = $t\n";
  while (cycle($t++, $m, $n, \@vol) || 1) {
    print $cs, text(@vol);
    print "t = $t\n";
  }
  print $cs, text(@vol);
  print "Finshed at t = $t\n";
}


# run a single cycle of frost simulation
sub cycle {
  my ($t, $m, $n, $data) = @_;
  my $v; # has vapor remaining

  if ($t % 2) {
    # boundary conditions -- do these seperately to keep
    # the checks out of the main loop
    # Corner
    $v = process($data->[-1][-1], $data->[-1][0],
                 $data->[ 0][-1], $data->[ 0][0]);
    # vertical seam
    for (my $y = 1; $y < $n - 2; $y += 2) {
      $v = process($data->[-1][$y], $data->[-1][$y+1],
                   $data->[ 0][$y], $data->[ 0][$y+1]) || $v;
    }
    # horizontal seam
    for (my $x = 1; $x < $m - 2; $x += 2) {
      $v = process($data->[$x][-1], $data->[$x+1][-1],
                   $data->[$x][ 0], $data->[$x+1][ 0]) || $v;
    }
    # center
    for (my $x = 1; $x < $m - 2; $x += 2) {
      for (my $y = 1; $y < $n - 2; $y += 2) {
        $v = process($data->[ $x ][$y], $data->[ $x ][$y+1],
                     $data->[$x+1][$y], $data->[$x+1][$y+1]) || $v;
      }
    }

  }
  else {
    for (my $x = 0; $x < $m; $x += 2) {
      for (my $y = 0; $y < $n; $y += 2) {
        $v = process($data->[ $x ][$y], $data->[ $x ][$y+1],
                     $data->[$x+1][$y], $data->[$x+1][$y+1]) || $v;
      }
    }
  }
  return $v;
}

# process a neigborhood
sub process {
  my ($vapor, $ice);
  foreach (@_) {
    $vapor++ if $_ eq VAPOR;
    $ice++   if $_ eq ICE;
  }

  # short-circuit if nothing to do.
  # faster, but it makes the output jumpy on my system.
  # things may look smoother without it.
  return 0 unless $vapor;

  if ($ice) {
    # freeze vapor
    @_[0..3] = map {$_ eq VAPOR ? ICE : $_} @_;
  }
  else {
    # randomly rotate vapor
    if (int rand(2)) {
      @_[0 .. 3] = @_[1, 3, 0, 2];
    }
    else {
      @_[0 .. 3] = @_[2, 0, 3, 1];
    }
  }

  return $ice ? 0 : $vapor;
}

# display volume as text
sub text {
  my $t = '';
  local $" = '';
  foreach (@_) {
    $t .= "@$_\n";
  }
  return $t;
}

