
my ($X, $Y, $rho, $seeds) = @ARGV;
$X ||= 40;
$Y ||= 20;
$rho ||= 0.2;
$seeds = 1 unless defined $seeds;
$|=1;

die "Board size must be even\n" unless $X %2 == 0 && $Y % 2 == 0;
die "Vapor density must be between 0 and 1\n" unless $rho <= 1 && $rho >= 0;

my $B = World::PPM->new($X, $Y)->random($rho, ".");
$B->cell($X/2, $Y/2, '*') if $seeds;
for (1 .. $seeds-1) {
  $B->cell(int(rand $X), int(rand $Y), '*');
}

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

# clear();
while (1) {
#  home();
  if ($t % 1 == 0) {
    open D, ">", sprintf("f%04d.ppm", $t) or die $!;
    $B->show(*D);
    warn "$t\n";
  }
  $B->do_for_neighborhoods($t++, \&rule);
#  my $z = <STDIN>;
}

sub clear { print "\e[H\e[2J" }
sub home  { print "\e[H\e[1J" }

sub rule {
#  die unless @_ == 4;
#  die if grep $_ eq "", @_;

  if (grep $_ eq "*", @_) {
    return map {$_ eq ' ' ? ' ' : '*'} @_;
  }

  # No stars
  #                      counterclockwise   clockwise
  my @rot = rand() < 0.5 ? (1, 3, 0, 2) : (2, 0, 3, 1);
  return @_[@rot];
}

package World;

sub new {
  my ($pkg, $x, $y, $ch) = @_;
  $ch = " " unless defined $ch;
  my @board =  ($ch x $x) x $y;
  bless {B => \@board, X => $x, Y => $y} => $pkg;
}

sub do_for_neighborhoods {
  my ($S, $t, $rule) = @_;
  my ($xmax, $ymax) = $S->size;
  my @newboard;
  my $p = $t % 2;
  for (my $y = $p; $y < $ymax; $y+=2){
    my @in = @{$S->{B}}[$y, ($y+1)%$ymax];
    my @out;
    for (my $x = $p; $x < $xmax; $x+=2) {
      my @in_nb;                # 'before' neighborhood
      for (0 .. 1) {
        push @in_nb, substr($in[$_], $x, 1), substr($in[$_], ($x+1)%$xmax, 1);
      }
      my @out_nb = $rule->(@in_nb);
#      die unless @out_nb == 4;
      $out[0] .= "$out_nb[0]$out_nb[1]";
      $out[1] .= "$out_nb[2]$out_nb[3]";
    }
    push @newboard, @out;
  }
  if ($p) {
    for (@newboard) {
      my $c = chop;
      $_ = "$c$_";
    }
    unshift @newboard, pop @newboard;
  }
  $S->{B} = \@newboard;
}

sub show {
  my $S = shift;
  my $fh = shift || *STDOUT;
  for (@{$S->{B}}) {
    print $fh $_, "\n";
  }
}

sub size {
  my $S = shift;
  ($S->{X}, $S->{Y});
}

sub cell {
  my ($S, $x, $y, $v) = @_;
  my $oldcell = substr($S->{B}[$y], $x, 1);
  if (defined $v) {
    substr($S->{B}[$y], $x, 1) = $v;
  }
  $oldcell;
}

sub random {
  my ($S, $rho, $v) = @_;
  for (@{$S->{B}}) {
    for my $x (0 .. length($_)-1) {
      substr($_, $x, 1) = $v if rand() < $rho;
    }
  }
  $S;
}

package World::PPM;
BEGIN {@ISA = 'World'}

sub show {
  my $S = shift;
  my $fh = shift || *STDOUT;
  my $ofh = select $fh;
  my $ohot = $|;
  $|=1;
  print "P6\n", join(" ", $S->size, 255), "\n";
  for (@{$S->{B}}) {
    my $s = $_;
    $s =~ s/ /\x0\x0\x0/g;            # black
    $s =~ s/\./\x0\x0\xff/g;          # blue
    $s =~ s/\*/\x0\xff\x0/g;          # green
    print $s;
  }
  $| = $ohot;
  select $ofh;
}

