#!/usr/bin/perl

# a simple minesweeper implement, ported from $ruby_source/sample/mine.rb

use strict;
use warnings;

use Ruby -all;

{
	package Board;
	use Ruby;
	use Ruby -base => 'Object';

	Array->alias('aref', '[]');
	Array->alias('aset', '[]=');

	our $Default = 46;
	our $Opened  = 43;
	our $Over    = 45;

	our @CHR = ('. ', '1 ', '2 ', '3 ', '4 ', '5 ', '6 ', '7 ', '8 ', 'M ', 'B ', '@@');

	__PACKAGE__->attr_accessor(qw(_hi _wi _m _ms _total
		_cx _cy _mc _over _data _state));

	sub info{
		my($self) = @_;

		$self->pos(0, $self->_hi);
		print "the rest: ", $self->_mc, "/", $self->_total, "    ";
		$self->pos();
	}

	sub clr{
		my($self) = @_;
		print "\e[2J";
	}

	sub pos{
		my($self, $x, $y) = @_;
		$x ||= $self->_cx;
		$y ||= $self->_cy;
		printf "\e[%d;%dH", $y+1, $x*2+1;
	}
	sub colorstr{
		my($self, $id, $s) = @_;
		printf "\e[%dm%s\e[0m", $id, $s;
	}

	sub put{
		my($self, $x, $y, $col, $str) = @_;

		$self->pos($x, $y);
		$self->colorstr($col, $str);
		$self->info();
	}

	sub new{
		my($class, $h, $w, $m) = @_;

		my $self = $class->SUPER::new();

		$self->_hi = $h;
		$self->_wi = $w;
		$self->_m  = $m;

		$self->reset;

		return $self;
	}

	sub reset{
		my($self) = @_;

		Kernel->srand();

		$self->_cx = 0;
		$self->_cy = 0;
		$self->_mc = $self->_m;
		$self->_over = false;
		$self->_data  = Array->new($self->_hi * $self->_wi);
		$self->_state = Array->new($self->_hi * $self->_wi);
		$self->_total = $self->_hi * $self->_wi;
		$self->_total->times(sub{
			my($i) = @_;
			$self->_data->aset($i, 0);
		});

		$self->_m->times(sub{
			while(true){
				my $j = Kernel->rand($self->_total - 1);
				if($self->_data->aref($j) == 0){
					$self->_data->aset($j, 1);
					last;
				}
			}
		});

		$self->clr;
		$self->pos(0, 0);
		$self->_hi->times(sub{
			my($y) = @_;
			$self->pos(0, $y);
			$self->colorstr($Default, $CHR[0] * $self->_wi);
		});

		$self->info();
	}

	sub mark{
		my($self) = @_;

		my $ix = $self->_wi * $self->_cy + $self->_cx;
		my $s = $self->_state->aref($ix);
		if($s == nil){
			$self->_state->aset($ix, "MARK");

			$self->_mc    -= 1;
			$self->_total -= 1;
			$self->put($self->_cx, $self->_cy, $Opened, $CHR[9]);
		}
		elsif($s == "MARK"){
			$self->_state->aset($ix, nil);

			$self->_mc    += 1;
			$self->_total += 1;

			$self->put($self->_cx, $self->_cy, $Default, $CHR[0]);
		}
		elsif($s == "OPEN"){
			return;
		}
	}

	sub open{
		my($self, $x, $y) = @_;
		$x ||= $self->_cx;
		$y ||= $self->_cy;

		my $wi = $self->_wi;
		my $hi = $self->_hi;
		my $state = $self->_state;

		if($state->aref($wi * $y + $x) == "OPEN"){ return 0 }
		if($state->aref($wi * $y + $x) == nil) {
			$self->_total -= 1;
		}
		if($state->aref($wi * $y + $x) == "MARK"){
			$self->_mc += 1;
		}

		$self->_state->aset($wi * $y + $x, "OPEN");

		if($self->fetch($x, $y) == 1){
			$self->_over = 1;
			return;
		}

		my $c = $self->count($x, $y);
		$self->put($x, $y, $Opened, $CHR[$c]);

		return if $c != 0;

		if($x > 0 && $y > 0)        { $self->open($x-1, $y-1) }
		if($y > 0)                  { $self->open($x,   $y-1) }
		if($x < $wi-1 && $y > 0)    { $self->open($x+1, $y-1) }
		if($x > 0)                  { $self->open($x-1, $y)   }
		if($x < $wi-1)              { $self->open($x+1, $y)   }
		if($x > 0 && $y < $hi-1)    { $self->open($x-1, $y+1) }
		if($y < $hi-1)              { $self->open($x,   $y+1) }
		if($x < $wi-1 && $y < $hi-1){ $self->open($x+1, $y+1) }

		$self->pos();
	}

	sub fetch{
		my($self, $x, $y) = @_;

		if($x < 0)             { return 0 }
		elsif($x >= $self->_wi){ return 0 }
		elsif($y < 0)          { return 0 }
		elsif($y >= $self->_hi){ return 0 }
		else{
			$self->_data->aref($y * $self->_wi + $x);
		}
	}
	sub count{
		my($self, $x, $y) = @_;

		$self->fetch($x-1, $y-1) + $self->fetch($x,   $y-1) + $self->fetch($x+1, $y-1) +
		$self->fetch($x-1, $y)   +                          + $self->fetch($x+1, $y)   +
		$self->fetch($x-1, $y+1) + $self->fetch($x,   $y+1) + $self->fetch($x+1, $y+1);
	}

	sub over{
		my($self, $win) = @_;

		$self->quit();
		unless($win){
			$self->pos();
			print $CHR[11];
		}
		$self->pos(0, $self->_hi);

		if($win){
			print "*** YOU WIN ! ***";
		}
		else{
			print "*** GAME OVER ***";
		}
	}

	sub is_over{
		my($self) = @_;
		my $remain = ($self->_mc + $self->_total == 0);

		if($self->_over || $remain){
			$self->over($remain);
			return true;
		}
		else{
			return false;
		}
	}

	sub quit{
		my($self) = @_;

		$self->_hi->times(sub{ my($y) = @_;
			$self->pos(0, $y);
			$self->_wi->times(sub{ my($x) = @_;
			
				$self->colorstr(
					$self->_state->aref($y*$self->_wi+$x) == "MARK"
						? $Default : $Over,
					$self->fetch($x, $y) == 1
						? $CHR[10] : $CHR[ $self->count($x, $y) ]);
			});
		});
	}
	sub down
	{
		my($self) = @_;

		if($self->_cy < $self->_hi-1){
			$self->_cy += 1;
			$self->pos();
		}
	}
	sub up
	{
		my($self) = @_;

		if($self->_cy > 0){
			$self->_cy -= 1;
			$self->pos();
		}
	}
	sub left
	{
		my($self) = @_;

		if($self->_cx > 0){
			$self->_cx -= 1;
			$self->pos();
		}
	}
	sub right
	{
		my($self) = @_;
		if($self->_cx < $self->_wi-1){
			$self->_cx += 1;
			$self->pos();
		}
	}
}

my $bd = Board->new(10, 10, 10);


system "stty raw -echo";

while(true){
	$_ = getc;

	if($_ eq 'n'){
		$bd->reset;
	}
	elsif($_ eq 'm'){
		$bd->mark;
	}
	elsif($_ eq 'j'){
		$bd->down;
	}
	elsif($_ eq 'k'){
		$bd->up;
	}
	elsif($_ eq 'h'){
		$bd->left;
	}
	elsif($_ eq 'l'){
		$bd->right;
	}
	elsif($_ eq ' '){
		$bd->open;
	}
	elsif($_ eq 'q'){
		$bd->quit;
		last;
	}

	if($bd->is_over){
		my $c;
		print "\nquit?(y/n) ";
		1 while(($c = lc getc) !~ /^[yn]/);

		$c eq 'y' and last;

		$bd->reset;
	}
}

puts;

END{
	system "stty -raw echo";
}
