# 
# Here's mine -- it works best if you create a DOS window, set the font tiny,
# and size it moderately large.  Just run it as frost.pl for the defaults -- 1
# initial crystal, and 1/5 of the grid filled with vapor.  It's also kinda
# neat to just double-click the script icon and let it use your default
# console size.
# 
# It seems faster if I inline the functions and don't use the references, but
# it's a lot easier to read this way, I think.
# 
# Sorry to those who use UNIX, etc. -- I'd never used Win32::Console, so this
# seemed a good opportunity.
# 
# ###
# 
#!/usr/bin/perl
# 
# Command line arguments:
# ---------------------------------------------------------
#    -h <height>   | the desired height of the grid
#    -w <width>    | the desired width of the grid
#    -v <vapor>    | the desired number of vapor molecules
#    -c <crystals> | the desired initial number of crystals
#    -d            | require an ENTER between iterations
# ---------------------------------------------------------
#
# Defaults:
# ---------------------------------------------------------
#    height        | the current height of the buffer
#    width         | the current width of the buffer
#    vapor         | height * width / 5
#    crystals      | 1
#    delay         | do not delay between iterations
# ---------------------------------------------------------
#
# Example:
#    frost.pl -d -h 20 -w 20 -c 2 -v 250

use Win32::Console;

$HEIGHT   = 0;    $SPACE_CHR = " ";
$WIDTH    = 0;    $VAPOR_CHR = ".";
$VAPOR    = 0;    $CRYST_CHR = "+";
$CRYSTALS = 0;    $DELAY     = 0;

$CONSOLE = new Win32::Console;
$CONSOLE->Alloc;

while ($_ = shift @ARGV) {
  if    (/^-h/i) { $ref = \$HEIGHT }
  elsif (/^-w/i) { $ref = \$WIDTH }
  elsif (/^-v/i) { $ref = \$VAPOR }
  elsif (/^-c/i) { $ref = \$CRYSTALS }
  elsif (/^-d/i) { $DELAY = 1; next }
  else           { next }

  $_ = shift @ARGV || last;
  /\D/ && next;
  $$ref = $_;
}

if ($HEIGHT) {
  $HEIGHT += $HEIGHT % 2;
  if ($HEIGHT > ($CONSOLE->Size)[1]) {
    print "Cannot create a console of that height in this window.\n";
    exit;
  }
}
else {
  $HEIGHT = ($CONSOLE->Size)[1];
}

if ($WIDTH) {
  $WIDTH += $WIDTH % 2;
  if ($WIDTH > ($CONSOLE->Size)[0]) {
    print "Cannot create a console of that width in this window.\n";
    exit;
  }
}
else {
  $WIDTH = ($CONSOLE->Size)[0];
}

$CRYSTALS ||= 1;
if ($CRYSTALS > $HEIGHT * $WIDTH) {
  print "Too many crystals!\n";
  exit;
}

if ($VAPOR) {
  if ($VAPOR + $CRYSTALS > $HEIGHT * $WIDTH) {
    print "Too much vapor!\n";
    exit;
  }
}
else {
  $VAPOR = int($HEIGHT * $WIDTH / 5);
  if ($VAPOR + $CRYSTALS > $HEIGHT * $WIDTH) {
    $VAPOR = $HEIGHT * $WIDTH - $CRYSTALS;
  }
}

$CONSOLE->Window(1, 0, 0, $WIDTH - 1, $HEIGHT - 1);
$CONSOLE->Size($WIDTH, $HEIGHT);
$CONSOLE->Cursor(-1, -1, 1, 0);
$CONSOLE->Display;

@grid = &initialize_grid($WIDTH, $HEIGHT, $VAPOR, $CRYSTALS);

while (++$i) {
  $catstr = join "", map { @{$_} } @grid;
  $CONSOLE->WriteChar($catstr, 0, 0);
  if ($DELAY) {
    $CONSOLE->Cursor(0, 0);
    getc;
  }
  last unless $catstr =~ /\./;
  &transform_grid(\@grid, $i % 2);
}

getc unless $DELAY;

#-------------------------------

sub initialize_grid {
  my ($w, $h, $f, $c) = @_;
  my @o = ();
  my $i, $j, $n;

  for $i (0 .. $h - 1) {
    for $j (0 .. $w - 1) {
      $o[$i][$j] = $SPACE_CHR;
    }
  }

  $n = 0;
  while ($n < ($f + $c)) {
    $i = int(rand $h);
    $j = int(rand $w);
    next unless $o[$i][$j] eq $SPACE_CHR;
    $o[$i][$j] = $n++ >= $f ? $CRYST_CHR : $VAPOR_CHR;
  }

  @o;
}

sub transform_grid {
  my @o = @{$_[0]};

  my $w = scalar(@{$o[0]}) - 1;
  my $h = scalar(@o) - 1;
  my ($i, $j) = (0 - $_[1], 0 - $_[1]);

  while ($i < $h) {
    while ($j < $w) {
      &process_square(\($o[$i][$j], $o[$i + 1][$j],
                        $o[$i + 1][$j + 1], $o[$i][$j + 1]));
      $j += 2;
    }
    $i += 2;
    $j = -$_[1];
  }
}

sub process_square {
  my $tmp;

  if (&compare_list($VAPOR_CHR, @_)) {
    if (&compare_list($CRYST_CHR, @_)) {
      for (@_) {
        $$_ = $CRYST_CHR if $$_ eq $VAPOR_CHR;
      }
    }
    else {
      if (&compare_list($SPACE_CHR, @_)) {
        @_ = reverse @_ if (int(rand 2));

        $tmp = ${$_[0]};
        ${$_[0]} = ${$_[1]};
        ${$_[1]} = ${$_[2]};
        ${$_[2]} = ${$_[3]};
        ${$_[3]} = $tmp;
      }
    }
  }
}

sub compare_list {
  my $match = shift @_;
  for (@_) {
    return 1 if $match eq $$_;
  }
}
