# 
# Thanksgiving has the family in from out of town, so I wasn't even going 
# to attempt this week's quizzes.  Then I made a vital error, I read 
# them!  I couldn't stop thinking about the Expert, so I had to try it.  
# I didn't have much time, so it's not too pretty, but it seems to work.  
# Great quiz, MJD!
# 
# James Gray
# 
# Frost.pm
# 
#!/usr/bin/perl

package Frost;

use 5.6.0;
use strict;
use warnings;

# accept the grid as an array of arrays, an array of strings, or stats for
# random generation
sub new {
	my $class = shift;

	my $grid = [ ];
	if (ref($_[0]) eq 'ARRAY' && ref(${ $_[0] }[0]) eq 'ARRAY') {
		$grid = shift;
	}
	elsif (ref($_[0]) eq 'ARRAY') { push @$grid, shift foreach (@_); }
	elsif ($_[0] =~ /^[*. ]+$/) {
		push @$grid, [ split //, $_ ] foreach (@_);
	}
	else {
		my($ice, $vapor, $width, $height) = @_;
		$width ||= 80;
		$height ||= 22;
		my $total = $width * $height;
		# convert percents to counts
		$ice = sprintf "%.0f", $ice * $total if $ice < 1;
		$vapor = sprintf "%.0f", $vapor * $total if $vapor < 1;
		die "Too much ice or vapor.\n" if $ice + $vapor > $total;
		# random grid generation
		push @$grid, [ (' ') x $width ] foreach (1..$height);
		while ($ice) {
			my($x, $y) = (int(rand($width)), int(rand($height)));
			if ($$grid[$y][$x] eq ' ') {
				$$grid[$y][$x] = '*';
				$ice--;
			}
		}
		while ($vapor) {
			my($x, $y) = (int(rand($width)), int(rand($height)));
			if ($$grid[$y][$x] eq ' ') {
				$$grid[$y][$x] = '.';
				$vapor--;
			}
		}
	}
	die "Dimensions must be even.\n" if @$grid % 2 || @{ $$grid[0] } % 2;
	
	my $object = { Grid		=> $grid,
				   Tick		=> 0 };
	bless $object, $class;
}

sub get_ice {
	my $self = shift;
	my $ice = 0;
	for my $y (@{ $self->{Grid} }) {
		for my $x (@{ $y }) {
			$ice++ if $x eq '*';
		}
	}
	return $ice;
}

sub get_tick { return shift->{Tick}; }

sub get_vapor {
	my $self = shift;
	my $vapor = 0;
	for my $y (@{ $self->{Grid} }) {
		for my $x (@{ $y }) {
			$vapor++ if $x eq '.';
		}
	}
	return $vapor;
}

# returns true when ticks have no further effect
sub is_frozen { return !shift->get_vapor(); }

sub tick {
	my $self = shift;
	$self->{Tick}++;
	# walk regions freezing vapor or performing rotations
	for ( my $x = $self->{Tick} % 2 ? -1 : 0; $x < ($self->{Tick} % 2 ?
			@{ $self->{Grid}[0] } - 1 : @{ $self->{Grid}[0] }); $x += 2 ) {
		for ( my $y = $self->{Tick} % 2 ? -1 : 0; $y < ($self->{Tick} % 2 ?
				@{ $self->{Grid} } - 1 : @{ $self->{Grid} }); $y += 2 ) {
			my($x2, $y2) = ($x + 1, $y);
			my($x3, $y3) = ($x, $y + 1);
			my($x4, $y4) = ($x + 1, $y + 1);
			if ( $self->{Grid}[$y][$x] eq '*' ||
					$self->{Grid}[$y2][$x2] eq '*' ||
					$self->{Grid}[$y3][$x3] eq '*' ||
					$self->{Grid}[$y4][$x4] eq '*' ) {
				$self->{Grid}[$y][$x] = '*' if $self->{Grid}[$y][$x] eq '.';
				$self->{Grid}[$y2][$x2] = '*' if $self->{Grid}[$y2][$x2] eq '.';
				$self->{Grid}[$y3][$x3] = '*' if $self->{Grid}[$y3][$x3] eq '.';
				$self->{Grid}[$y4][$x4] = '*' if $self->{Grid}[$y4][$x4] eq '.';
			}
			elsif (rand() < .5) {
				( $self->{Grid}[$y][$x], $self->{Grid}[$y2][$x2],
				  $self->{Grid}[$y3][$x3], $self->{Grid}[$y4][$x4] ) =
													( $self->{Grid}[$y2][$x2],
													  $self->{Grid}[$y4][$x4],
													  $self->{Grid}[$y][$x],
													  $self->{Grid}[$y3][$x3] );
			}
			else {
				( $self->{Grid}[$y][$x], $self->{Grid}[$y2][$x2],
				  $self->{Grid}[$y3][$x3], $self->{Grid}[$y4][$x4] ) =
													( $self->{Grid}[$y3][$x3],
													  $self->{Grid}[$y][$x],
													  $self->{Grid}[$y4][$x4],
													  $self->{Grid}[$y2][$x2] );
			}
		}
	}
}

# for easy printing
sub to_string_array {
	my $self = shift;
	my @string_array = ( );
	push @string_array, join '', @{ $_ } foreach (@{ $self->{Grid} });
	return @string_array;
}

# make the module self-executing
if (!caller) {
	package main;
	
	use Term::ANSIColor;

	my $simulation;
        $SIG{ALRM} = sub { warn "TICKS: $simulation->{Tick}\n"; exit };
        alarm $ENV{TIMELIMIT} || 120;

	if (@ARGV) {
                 $simulation = Frost->new(@ARGV);
		while (1) {
			foreach ($simulation->to_string_array()) {
				# colorize for pretty output
				s/(\.+)/colored($1, 'blue on_black')/ge;
				s/(\*+)/colored($1, 'white on_black')/ge;
				print "$_\n" ;
			}
			# give statistics along with output
			print 'Tick: ', $simulation->get_tick(),
				  '  Ice: ', $simulation->get_ice(),
				  '  Vapor: ', $simulation->get_vapor(), "\n";
			last if $simulation->is_frozen();
#			sleep 1;
			$simulation->tick();
		}
	}
	else {
		print "\nUsage:  >Frost.pm ICE VAPOR [ WIDTH ] [HEIGHT]\n",
			  "  or    >Frost.pm LINE1 LINE2 ... LINEN\n\n",
			  "ICE and VAPOR can be exact counts or percentages of presence.\n",
			  "WIDTH and HEIGHT default to 80 and 22.\n",
			  "LINE# must be strings of only '*. ' defining the rows.\n\n";
	}


}

1;

