#!/usr/bin/perl
# Michael C. Toren <mct@toren.net>
# vim:set ts=4 sw=4 ai nobackup nowrap:

use strict;
use warnings;
open STDERR, ">", "tickout" or die $!;

use Curses;
sub cleanup { endwin; exit }
$SIG{TERM} = $SIG{HUP} = $SIG{INT} = $SIG{QUIT} = \&cleanup;
END { cleanup }

use constant VACUUM => 0;
use constant VAPOR => 1;
use constant ICE => 2;

my ($sizerows, $sizecols, $sizevec, $humidity,
	$universe, $alternateuniverse, $t, @art, @artattrib,
	$done);

sub usage { die "Usage: $0 <rows> <cols> [humidity]\n"; }

sub isodd ($)	{ ($_[0] % 2) == 1 }
sub iseven ($)	{ ($_[0] % 2) == 0 }

sub coord2vec { $_[0] * $sizecols + $_[1] }
sub vec2coord { int($_[0] / $sizecols), ($_[0] % $sizecols) }

sub getcell { vec (${$_[0]}, $_[1], 2) }
sub setcell { vec (${$_[0]}, $_[1], 2) = $_[2] }

sub _neighbor
{
	my ($r, $c) = vec2coord shift;
	$r = ($r + shift) % $sizerows;
	$c = ($c + shift) % $sizecols;
	return coord2vec $r, $c;
}

sub north($) { _neighbor shift, -1,  0 }
sub south($) { _neighbor shift,  1,  0 }
sub east($)  { _neighbor shift,  0,  1 }
sub west($)  { _neighbor shift,  0, -1 }

sub neighborhood
{
	my $v = shift;
	my ($r, $c) = vec2coord $v;

	return ($v, south $v, south east $v, east $v) if (iseven $r+$t && iseven $c+$t);
	return ($v, south $v, south west $v, west $v) if (iseven $r+$t && isodd  $c+$t);
	return ($v, north $v, north east $v, east $v) if (isodd  $r+$t && iseven $c+$t);
	return ($v, north $v, north west $v, west $v) if (isodd  $r+$t && isodd  $c+$t);
}

sub rotate
{
	if (int rand 2) { unshift @_, pop @_ }
	else { push @_, shift @_ }
	return @_;
}

sub output
{
	for my $r (0 .. $sizerows-1) {
		for my $c (0 .. $sizecols-1) {
			my $cell = (getcell \$universe, coord2vec $r, $c) || 0;
			attrset $artattrib[$cell];
			addstr $r, $c, $art[$cell];
		}
	}
	curs_set 0;
	refresh;
	warn "$t ", scalar(localtime), "\n";
}

$sizerows = shift or usage;
$sizecols = shift or usage;
$sizevec = coord2vec $sizerows-1, $sizecols-1;
$humidity = shift;

die "Sorry, rows and columns must be even integers\n"
	unless (iseven $sizerows and iseven $sizecols);
die "Sorry, minimum value for rows and columns is 4\n"
	unless ($sizerows >= 4 and $sizecols >= 4);

$humidity = 20 unless (defined $humidity);

# initialize the universe.
$universe = $alternateuniverse = 0;
setcell \$universe, $_, (rand 100 < $humidity) ? VAPOR : VACUUM for (0..$sizevec);
setcell \$universe, coord2vec ($sizerows / 2, $sizecols / 2), ICE;

initscr;
noecho;
cbreak;
nonl;
@art = (" ", ".", "*");

start_color;
init_pair 1, COLOR_BLACK, COLOR_BLACK;
init_pair 2, COLOR_BLUE,  COLOR_BLACK;
init_pair 3, COLOR_WHITE, COLOR_BLACK;
@artattrib = (COLOR_PAIR(1)|A_NORMAL,
					COLOR_PAIR(2)|A_NORMAL,
					COLOR_PAIR(3)|A_BOLD);
$t = 0;
$SIG{ALRM} = sub { warn "TICKS: $t\n"; exit };
alarm $ENV{TIMELIMIT} || 120;

while (! $done)
{
	output;
	$done = 1;

	for (my $r = $t % 2; $r < $sizerows; $r += 2)
	{
		for (my $c = $t % 2; $c < $sizecols; $c += 2)
		{
			my $v = coord2vec $r, $c;
			my @n = neighborhood $v;

			if (grep { getcell(\$universe, $_) == ICE } @n)
			{
				for my $i (@n)
				{
					my $cell = getcell \$universe, $i;
					setcell \$alternateuniverse, $i, 
						($cell == VAPOR ? ICE : $cell);
				}
				next;
			}

			my @foo;

			if (grep { getcell(\$universe, $_) == VAPOR } @n) {
				@foo = rotate @n;
				$done = 0;
			} else {
				@foo = @n;
			}

			setcell \$alternateuniverse, $foo[$_],
				getcell \$universe, $n[$_]
					for (0 .. $#foo);
		}
	}

	$universe = $alternateuniverse;
	$t++;
}
output;

