#!/usr/bin/perl
# Here's my effort. It's not super optimised, nor is it that elegant. I
# do like it though.
# 
# It uses a single dimensional array to hold the environment and
# predefined the neighbourhoods as array slices. It uses GD for the
# frames and ImageMagick to create an animated gif at the end.
# 
# It has an optional $shaded variable to make it look pretty.
# 
# I hope you like it.
# 
# Steve

use Data::Dumper;
use GD;
use Image::Magick;

#######################

$mx = 128;			# sim dimensions
$my = 128; 

$pix = 2;			# pixel size

$d = 0.25;			# vapour density

$stamp = 20;			# image every $stamp iterations

$shaded = 1;

#######################

die "dimensions divisible by four only" if $mx %4 || $my %4;

# states are 0 => nothing, 1 => vapour, 2 => frost

# create a grid of references, a blank old environment
# and a randomly populated new environment of vapour

$i = 0;

for $y (0..$my-1) {

  for $x (0..$mx-1) {

    $gref[$x][$y] = $i;
    $oldw[$i] = 0;
    $neww[$i] = (rand() < $d) ? 1 : 0;

    $i++;
  }
}

# stick a frost in the middle

$neww[$gref[$mx/2][$my/2]] = 2; 

# an array of delta for the neighbourhoods

@dea = ( [ (0,1),(0,1) ],	# brackets intentional
         [ (0,1),(-1,0) ],
         [ (-1,0),(0,1) ],
	 [ (-1,0),(-1,0) ]);

# just slip up left
# 
# @dea = ( [ (0,1),(0,1) ],
#          [ (-1,0),(-1,0) ] );

# and count them

$envc = @dea;

# define clockwise and anti clockwise for:
# 
# 0 2
# 1 3

@clock = (1,3,0,2);
@antic = (2,0,3,1);

# create even lists
#
# eg. mx = 10 => 0,2,4,6,8

@sx = map {$_*2} (0..($mx/2)-1);
@sy = map {$_*2} (0..($my/2)-1);

# for each environment delta set
# define the list of list of indecies in the environment

for $de (@dea) {

  my @enl;

  my ($fdx,$ldx,$fdy,$ldy) = @$de;

  for $px (@sx) {

    my @x = map {$px+$_} ($fdx,$ldx);

    my @slice = @gref[@x];

    for $py (@sy) {
      
      my @y = map {$py+$_} ($fdy,$ldy);

      my @en = map {@$_[@y]} @slice;

      push @enl, [@en];
    }
  }
  
  push @envs, [@enl];
}

# create list of crossed neightbourhoodss

if ($shaded) {

  $i = 0;

  $#cross = ($mx*$my)-1;

  for $y (0..$my-1) {

    for $x (0..$mx-1) {

      $cross[$i++] = [($gref[$x][$y-1],
	  	       $gref[$x][($y+1)%$my],
		       $gref[$x-1][$y],
		       $gref[($x+1)%$mx][$y])];
    }			     
  }
}

my $anim = new Image::Magick;

$image = new GD::Image($mx*$pix, $my*$pix);
  
$bg = $image->colorAllocate(0,0,0);

if ($shaded) {

  $fshades[0] = $bg;
  $fshades[1] = $image->colorAllocate(255,255,255);
  $fshades[2] = $image->colorAllocate(239,255,239);
  $fshades[3] = $image->colorAllocate(223,255,223);
  $fshades[4] = $image->colorAllocate(207,255,207);
  $fshades[5] = $image->colorAllocate(191,255,191);

#    $vshades[0] = $bg;
#    $vshades[1] = $image->colorAllocate(0,50,50);
#    $vshades[2] = $image->colorAllocate(0,100,100);
#    $vshades[3] = $image->colorAllocate(0,150,150);
#    $vshades[4] = $image->colorAllocate(0,200,200);
#    $vshades[5] = $image->colorAllocate(0,255,255);

  $vshades[0] = $bg;

  for $j (1..12) {

    $vshades[$j] = $image->colorAllocate(0,$j*19.5,$j*19.5);
  }

} else {

  $colours[0] = $bg;
  $colours[1] = $image->colorAllocate(0,255,255);
  $colours[2] = $image->colorAllocate(255,255,255);
}

sub show;

show();  # t=0

# whilst there is still vapour in the system

while($vapour = grep {$_ == 1} @neww) {

  printf "time = %d vapour = %d\n", ++$t, $vapour;

  @oldw = @neww;

  # process each environment from old to new

  for $e (@{$envs[rand $envc]}) {

    @neww[@$e] = process(@oldw[@$e]);
  }

  # only add every so often

  show() unless $t % $stamp;
}

show(); # final frame

$anim->Set(delay => 10, loop => 1);

$rc = $anim->Write('frost.gif');
die $rc if $rc;

# sub to make an image and add it to the animation

sub show {

  my $i;

  my $i;

  for my $y (0..$my-1) {
    for my $x (0..$mx-1) {

      my ($ix,$iy) = ($x*$pix,$y*$pix);

      if ($shaded) {

	if ($neww[$i] == 2) {
	  
	  my $tcol = grep {$_ == 2} @neww[@{$cross[$i]}];
	  
	  $tcol++; # for self

	  $image->filledRectangle($ix,$iy,$ix+$pix,$iy+$pix,$fshades[$tcol]);

	} else { 

	  my $tcol = grep {$_ == 1} @neww[@{$cross[$i]}];
	  
	  $tcol += 8 if $neww[$i]; # == 1

	  $image->filledRectangle($ix,$iy,$ix+$pix,$iy+$pix,$vshades[$tcol]);
	}

      } else {

	$image->filledRectangle($ix,$iy,$ix+$pix,$iy+$pix,$colours[$neww[$i]]);
      }
      
      $i++;
    }
  }

  my $rc = $anim->BlobToImage($image->png);
  die $rc if $rc;
}

# sub to process environment

sub process {

  my @env = @_;

  # look through and enfrost all vapour if a vapour is found

  for (@env) {

    return map { $_ == 1 ? 2 : $_ } @env if $_ == 2;
  }

  # return rotated environment otherwise

  return (rand() < 0.5) ? @env[@clock] : @env[@antic];
}
