#!/usr/bin/perl -w
# 智慧盤. 用 hjkl 四個鍵移動棋子, r 鍵打亂盤面, ^l 重畫盤面, ESC 離開.
# 畫面左上角顯示目前的盤面有多亂 (0 表示完全排整齊了)
# http://www.cyut.edu.tw/~ckhung/b/pl/

use strict;
require "sitio";

my (%board);
my ($row, $col, $c, $ent, $empty);

$board{"height"} = $#ARGV >= 0 ? $ARGV[0] : 4;
$board{"width"} = $#ARGV >= 1 ? $ARGV[1] : 4;

# Perl 內沒有真正的二維陣列, 我們用 hash 來模擬二維陣列.
# $board{"r0c3"} 記載的是: 棋盤上第 0 列第 3 行這一格目前擺的是那個數字.
for ($row=0; $row<$board{"height"}; ++$row) {
    for ($col=0; $col<$board{"width"}; ++$col) {
	$board{"r${row}c${col}"} = $row * $board{"width"} + $col + 1;
    }
}

# 請仔細研究下句: 把棋盤上的右下角那一格清成空白.
$empty = "";
$board{ sprintf("r%dc%d", $board{"height"}-1, $board{"width"}-1) } = $empty;

redraw();

while (1) {
    $ent = entropy();
    gotorc(23, 1);
    print $ent ? '      ' : '成功!', "\n";
    gotorc(1,1);
    aprintf($ent ? "31" : "36", "%3d", $ent);
    $c = getkey();
    if (index("hjkl", $c) >= 0) {
	moveblank($c);
    } elsif ($c eq "r") {
	stir(80);
    } elsif ($c eq "\x0c" || $c eq "\x12") {
	redraw();
    } elsif ($c eq "\x1b") {
	last;
    }
}
if ($ent) {
    gotorc(23, 1);
    print("放棄!\n")
} else {
    gotorc(24, 1);
}

sub redraw {
    my ($row, $col);

    clearscr();
    for ($row=0; $row<$board{"height"}; ++$row) {
	for ($col=0; $col<$board{"width"}; ++$col) {
	    drawcell($row, $col);
	}
    }
}

sub drawcell {
    my ($row, $col) = @_;
    my ($r0, $c0) = (
	(24 - $board{"height"}*2) / 2,
	(80 - $board{"width"}*4) / 2
    );

    gotorc($row*2+$r0, $col*4+$c0);
    aprintf("31;46", "%2s", $board{"r${row}c${col}"});
}

sub moveblank {
    my ($dir) = @_;
    my ($row, $col) = blankpos();
    my ($row_new, $col_new) = ($row, $col);

    if ($dir eq "h") {		# chess moves to the left; blank to the right
	return if ++$col_new >= $board{"width"};
    } elsif ($dir eq "j") {	# chess moves downward; blank upward
	return if --$row_new < 0;
    } elsif ($dir eq "k") {	# chess moves upward; blank downward
	return if ++$row_new >= $board{"height"};
    } elsif ($dir eq "l") {	# chess moves to the right; blank to the left
	return if --$col_new < 0;
    } else {
	return;
    }
    $board{"r${row}c${col}"} = $board{"r${row_new}c${col_new}"};
    $board{"r${row_new}c${col_new}"} = $empty;
    drawcell($row, $col);
    drawcell($row_new, $col_new);
}

sub blankpos {
    my ($row, $col);

    # 作業: 試把下面的迴圈改成 foreach 迴圈, 並用 regexp 從
    # %board 的 keys 當中取出行與列數. 這樣就不必用 label 了.
    locate:
    for ($row=0; $row<$board{"height"}; ++$row) {
	for ($col=0; $col<$board{"width"}; ++$col) {
	    last locate if ($board{"r${row}c${col}"} eq $empty);
	}
    }
    return ($row, $col);
}

sub stir {
    my ($count) = @_;

    for ( ; $count>0; --$count) {
	moveblank(substr("hjkl", int(rand 4), 1));
    }
}

sub entropy {
    my ($ent, $row, $col, $r_dst, $c_dst);

    $ent = 0;
    for ($row=0; $row<$board{"height"}; ++$row) {
	for ($col=0; $col<$board{"width"}; ++$col) {
	    next unless $board{"r${row}c${col}"};
	    # 按照它的數值, 它本來應該放到那裡去?
	    $r_dst = int( ($board{"r${row}c${col}"}-1) / $board{"width"} );
	    $c_dst = ($board{"r${row}c${col}"}-1) % $board{"width"};
	    # 把誤差累加入 $ent.
	    $ent += abs($row - $r_dst) + abs($col - $c_dst);
	}
    }
    return $ent;
}

