#!/usr/bin/env qore

%requires ncurses

%require-our
%enable-all-warnings

# ncurses example program ported to qore by David Nichols

#/*
# *	Name: Towers of Hanoi.
# *
# *	Desc:
# *		This is a playable copy of towers of hanoi.
# *		Its sole purpose is to demonstrate my Amiga Curses package.
# *		This program should compile on any system that has Curses.
# *		'hanoi'		will give a manual game with 7 playing pieces.
# *		'hanoi n'	will give a manual game with n playing pieces.
# *		'hanoi n a' will give an auto solved game with n playing pieces.
# *
# *	Author: Simon J Raybould	(sie@fulcrum.bt.co.uk).
# * 	(This version has been slightly modified by the ncurses maintainers.)
# *
# *	Date: 05.Nov.90
# *
# * $Id: hanoi.q,v 1.3 2006/04/27 06:04:00 david_nichols Exp $
# */

# global window variable
our $w;

# half-second delay
const delay        = 500000;

const NPEGS        = 3;	/* This is not configurable !! */
const MINTILES     = 3;
const MAXTILES     = 9;
const DEFAULTTILES = 7;
const TOPLINE      = 6;
const BASELINE     = 16;
const LEFTPEG	   = 19;
const MIDPEG	   = 39;
const RIGHTPEG	   = 59;

sub LENTOIND($x)
{
    return ($x-1)/2;
}

sub OTHER($a, $b)
{
    return 3-($a+$b);
}

our $Pegs = ();

our $PegPos = (LEFTPEG, MIDPEG, RIGHTPEG);

our $TileColour =
    (
     COLOR_WHITE,
     COLOR_GREEN,		/* Length 3 */
     COLOR_MAGENTA,		/* Length 5 */
     COLOR_RED,			/* Length 7 */
     COLOR_BLUE,			/* Length 9 */
     COLOR_CYAN,			/* Length 11 */
     COLOR_YELLOW,		/* Length 13 */
     COLOR_GREEN,		/* Length 15 */
     COLOR_MAGENTA,		/* Length 17 */
     COLOR_RED,			/* Length 19 */
     );

our $NMoves = 0;

sub main()
{
    my ($NTiles, $FromCol, $ToCol, $AutoFlag);
    $AutoFlag = False;

    if (elements $ARGV)
    {
	$NTiles = int(shift $ARGV);
	if (exists $ARGV[0])
	    if ($ARGV[0] != "a")
	        Usage();
	    else
		$AutoFlag = True;

	if ($NTiles > MAXTILES || $NTiles < MINTILES) {
	    printf("Range %d to %d\n", MINTILES, MAXTILES);
	    exit(1);
	}
    }
    else
	$NTiles = DEFAULTTILES;

    $w = new Window();
    if ($w.getLines() < 24) {
	endwin();
	printf("Min screen length 24 lines\n");
	exit(1);
    }
    if ($AutoFlag) {
	curs_set(0);
	#$w.leaveok(True);	/* Attempt to remove cursor */
    }
    InitTiles($NTiles);
    DisplayTiles();
    if ($AutoFlag) {
	do {
	    AutoMove(0, 2, $NTiles);
	} while (!Solved($NTiles));
	sleep(2);
    } else {
	echo();
	for (;;) {
	    if (GetMove(\$FromCol, \$ToCol))
		break;
	    if (InvalidMove($FromCol, $ToCol)) {
		$w.mvaddstr($w.getLines() - 3, 0, "Invalid Move !!");
		$w.refresh();
		beep();
		continue;
	    }
	    MakeMove($FromCol, $ToCol);
	    if (Solved($NTiles)) {
		$w.mvprintw($w.getLines() - 3, 0,
			    "Well Done !! You did it in %d moves", $NMoves);
		$w.refresh();
		sleep(5);
		break;
	    }
	}
    }
}

sub InvalidMove($From, $To)
{
    if ($From >= NPEGS)
	return True;
    if ($From < 0)
	return True;
    if ($To >= NPEGS)
	return True;
    if ($To < 0)
	return True;
    if ($From == $To)
	return True;
    if (!$Pegs[$From].Count)
	return True;
    if ($Pegs[$To].Count &&
	$Pegs[$From].Length[$Pegs[$From].Count - 1] >
	$Pegs[$To].Length[$Pegs[$To].Count - 1])
	return True;
    return False;
}

sub InitTiles($NTiles)
{
    my ($Size, $SlotNo);

    $Size = $NTiles * 2 + 1;
    $SlotNo = 0;
    for (; $Size >= 3; $Size -= 2)
	$Pegs[0].Length[$SlotNo++] = $Size;

    $Pegs[0].Count = $NTiles;
    $Pegs[1].Count = 0;
    $Pegs[2].Count = 0;
}

sub DisplayTiles()
{
    my ($Line, $peg, $SlotNo, $TileBuf);

    $w.erase();
    $w.mvaddstr(1, 24, "T O W E R S   O F   H A N O I");
    $w.mvaddstr(3, 34, "SJR 1990");
    $w.mvprintw(19, 5, "Moves : %d", $NMoves);
    $w.attrset(A_REVERSE);
    $w.mvaddstr(BASELINE, 8,
	     "                                                               ");

    for ($Line = TOPLINE; $Line < BASELINE; $Line++) {
	$w.mvaddch($Line, LEFTPEG, ' ');
	$w.mvaddch($Line, MIDPEG, ' ');
	$w.mvaddch($Line, RIGHTPEG, ' ');
    }
    $w.mvaddch(BASELINE, LEFTPEG, '1');
    $w.mvaddch(BASELINE, MIDPEG, '2');
    $w.mvaddch(BASELINE, RIGHTPEG, '3');
    $w.attrset(A_NORMAL);

    /* Draw tiles */
    for ($peg = 0; $peg < NPEGS; $peg++) {
	for ($SlotNo = 0; $SlotNo < $Pegs[$peg].Count; $SlotNo++) {

	    $TileBuf = "";

	    for (my $i = 0; $i < $Pegs[$peg].Length[$SlotNo]; $i++)
		$TileBuf += " ";

	    if (has_colors())
	    {
		#my $c = $TileColour[LENTOIND($Pegs[$peg].Length[$SlotNo])];
		#$w.printw("color=%5d", $c);
		$w.setBackgroundColor($TileColour[LENTOIND($Pegs[$peg].Length[$SlotNo])]);
		#$w.printw(", color=%5d", $c);
	    }
	    else
		$w.attrset(A_REVERSE);

	    $w.mvaddstr(BASELINE - ($SlotNo + 1),
		     ($PegPos[$peg] - $Pegs[$peg].Length[$SlotNo] / 2),
		     $TileBuf);
	}
    }
    $w.attrset(A_NORMAL);
    $w.refresh();
}

sub GetMove($From, $To)
{
    $w.mvaddstr($w.getLines() - 3, 0, "Next move ('q' to quit) from ");
    $w.clrtoeol();
    $w.refresh();

    if (($From = $w.getch()) == ord('q'))
	return True;
    $From -= (ord('0') + 1);
    $w.addstr(" to ");
    $w.clrtoeol();
    $w.refresh();

    if (($To = $w.getch()) == ord('q'))
	return True;
    $To -= (ord('0') + 1);

    $w.refresh();
    usleep(delay);

    $w.move($w.getLines() - 3, 0);
    $w.clrtoeol();
    $w.refresh();
    return False;
}

sub MakeMove($From, $To)
{
    $Pegs[$From].Count--;
    $Pegs[$To].Length[$Pegs[$To].Count] = $Pegs[$From].Length[$Pegs[$From].Count];
    $Pegs[$To].Count++;
    $NMoves++;
    DisplayTiles();
}

sub AutoMove($From, $To, $Num)
{
    if ($Num == 1) {
	MakeMove($From, $To);
	usleep(delay);
	return;
    }
    AutoMove($From, OTHER($From, $To), $Num - 1);
    MakeMove($From, $To);
    usleep(delay);
    AutoMove(OTHER($From, $To), $To, $Num - 1);
}

sub Solved($NumTiles)
{
    for (my $i = 1; $i < NPEGS; $i++)
	if ($Pegs[$i].Count == $NumTiles)
	    return True;
    return False;
}

sub Usage()
{
    printf("Usage: hanoi [<No Of Tiles>] [a]\n");
    printf("The 'a' option causes the tower to be solved automatically\n");
    exit();
}

main();