#!/usr/local/bin/perl -w
# Dan Boger
# 11/29/02
# Simulate frost, as defined in qotw #7, expert

use strict;

# define our parameters
# when to end the simulation: -1 means continue until all water is frozen
use constant LASTTICK => -1;

# print output after each tick
use constant VERBOSE  => 1;

# size of our field
use constant SIZEX    => 100;
use constant SIZEY    => 100;

# random distribution, or preset?  preset at DATA if RAND is 0
use constant RAND     => 1;
# percentage of vapor in a random field
use constant VAPORPER => 20;

# text output, or png?
use constant TEXTOUT  => 1;
# scale - pixles/particle
use constant SCALE    => 1;

unless (TEXTOUT) { # we're outputing pics - use GD
  require GD;
  import GD;
}

# read/generate our initial field
my ($grid, $vapor) = &ReadInitial();

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

&PrintGrid($grid, $tick) if VERBOSE;

# do the work - run Tick until there's no vapor læft, our we're out of time
while ($vapor > 0) {
  print "Tick ", ++$tick, " ($vapor vapor particles left)\n";
  $vapor = &Tick($grid, $tick);
  &PrintGrid($grid, $tick) if VERBOSE;
  last if $tick >= LASTTICK and LASTTICK != -1;
}

print "\n\n";
print "No more vapor!\n\n" if $vapor == 0;
&PrintGrid($grid, 'End');
exit;

sub ReadInitial {
  my $grid = [];
  my $line = 0;
  my $vapor = 0;

  if (not RAND) { # read preset field from DATA
    while (<DATA>) {
      s/[^ o*]+//g;
      next if /^$/;
      $vapor += tr/o/o/; # count initial vapor
      &SetGrid($grid,0,$line++,(split //)[0..SIZEX-1]);
      last if $line > SIZEY;
    }
  } else { # random grid, generate it
    foreach my $x (0 .. SIZEX-1) {
      foreach my $y (0 .. SIZEY-1) {
        $vapor += &SetGrid($grid, $x, $y, (rand(1) < VAPORPER/100 ? "o" : " ")) eq 'o';
      }
    }

    # single ice particle in the middle
    &SetGrid($grid, SIZEX/2, SIZEY/2, "*");
  }
  return ($grid, $vapor);
}

sub Tick {
  my ($grid, $tick) = @_;
  my ($x, $y);
  my $vapor = 0;

  # loop on each division, and have the starting point alternate by the ticks
  for ($y = $tick%2; $y < SIZEY; $y+=2) {
    for ($x = $tick%2; $x < SIZEX; $x+=2) {
      # keep track of how much vapor we have left
      $vapor += &Freeze($grid, [$x, $y], [$x+1, $y], [$x+1, $y+1], [$x, $y+1]);
    }
  } 

  return $vapor;
}

sub PrintGrid {
  my ($grid, $tick) = @_;

  if (TEXTOUT) { # text mode
    local $" = '';

    foreach my $y (0 .. SIZEY) {
      print "  ";
      foreach my $x (0 .. SIZEX) {
        print &GetGrid($grid, $x, $y);
      }
      print "\n";
    }
  } else { # GD mode
    print "Saving image\n";

    my $im    = new GD::Image(SIZEX*SCALE,SIZEY*SCALE);
    my %color;
    $color{'.'} = $im->colorAllocate(0,0,0);
    $color{'o'} = $im->colorAllocate(0  ,0  ,255);
    $color{'*'} = $im->colorAllocate(255,255,255);

    foreach my $y (0 .. SIZEY) {
      foreach my $x (0 .. SIZEX) {
        $im->rectangle($x*SCALE, $y*SCALE, ($x+1)*SCALE-1, ($y+1)*SCALE-1, 
	               $color{&GetGrid($grid, $x, $y)});
      }
    }

    open(SAVE,">7e.$tick.png") or die "Can't write png: $!";
    binmode(SAVE);
    print SAVE $im->png;
    close SAVE;
    print "Saved to 7e.$tick.png\n";
  }
}

sub Freeze {
  my ($grid, @coords) = @_;
  my @vapor;
  my $ice = 0;

  # look at all our coords, keep track of any vapor we find, and if we have any ice at all
  foreach (@coords) {
    my $particle = &GetGrid($grid,$_->[0],$_->[1]);
    push @vapor, $_ if $particle eq 'o';
    $ice = 1 if $particle eq '*';
  }

  return 0 unless @vapor; # if we have no water vapor, there's nothing to do

  if ($ice) { # we have some ice, so freeze all the vapor
    foreach (@vapor) {
      &SetGrid($grid, $_->[0], $_->[1], "*");
    }
    return 0;  # there's no vapor left here now!
  } else { # we have no ice, so just rotate the vapor
    my @current;

    # save current particle locations    12
    #                                    43
    foreach (@coords) {
      push @current, &GetGrid($grid,$_->[0],$_->[1]);
    }

    # figure out which direction we're turning
    my $dir = rand > .5 ? 1 : -1; # -1 : 41  1   : 23
                                  # cw   32  ccw   14
    # rotate @current according to $dir
    if ($dir == 1) {
      push @current, shift @current;
    } else {
      unshift @current, pop @current;
    }

    # save each particle in it's new location
    foreach (0 .. 3) {
      &SetGrid($grid,$coords[$_]->[0], $coords[$_]->[1], $current[$_]);
    }

    return @vapor;
  }
}

sub SetGrid {
  # this is it's own sub so we can easily modify it to use a file or database based grid
  my ($grid, $x, $y, @vals) = @_;
  $x = int($x);
  $y = int($y);

  # allow us to set multiple values in one row with one call
  foreach my $val (@vals) {
    # make sure we're cyclic
    $x = 0 if $x >= SIZEX;
    $y = 0 if $y >= SIZEY;
    $x = SIZEX-1 if $x < 0;
    $y = SIZEY-1 if $y < 0;

    $grid->[$y]->[$x++] = $val;
  }

  return $vals[0];
}

sub GetGrid {
  # this is it's own sub so we can easily modify it to use a file or database based grid
  my ($grid, $x, $y) = @_;

  # make sure we're cyclic
  $x = 0 if $x >= SIZEX;
  $y = 0 if $y >= SIZEY;
  $x = SIZEX-1 if $x < 0;
  $y = SIZEY-1 if $y < 0;

  return $grid->[$y]->[$x];
}

__DATA__
 
1 ...o.o.o..
2 .o..o....o
3 .....o...o
4 o.o..o..o.
5 ...o*..o..
6 ..o..o....
7 o......o..
8 ..........
9 ..o...o..o
10....o.ooo.
