/* hanoi.c
**
** Towers of Hanoi for the Amiga, by Ali Ozer (ali@Score.Stanford.Edu).
** Originally written June '86, revised February '87. This program may be
** freely distributed. Please do not delete the author's name & picture: 8-)
**
** This program will create a window within the workbench screen and start
** solving the Towers of Hanoi problem (with 5 disks). The main purpose
** of this program is to sit in the side and absorb unused cycles --- By
** default it runs at priority -20... 
** 
** This program can be started from the CLI or the Workbench. If started
** from the CLI, it will accept command line arguments to change the various
** parameters. (Give "?" as an argument to see what arguments are expected.)
** If started from the Workbench, the program will open a window and explicitly
** ask for the parameters. Hit <return> or ctrl-\ to use the default values.
**
** The idea for this program came from a similar program that runs on some
** Xerox Lisp machine. (I think it was some Xerox machine, I just saw it
** run one day as I was watching a friend work on one of them...) 
**
** Future improvements to this code might include (if anyone wishes to
** play with this program) making the disks look better and making them
** move pixel by pixel instead of jumping as they do now. 
**
** Thanks to Lee Taran (who was a few weeks ahead of me in solving the great
** mysteries of the Rom Kernel Manual, back in May '86, when we first got the
** Amiga --- Most of the initialization code is from her), and Tom Rokicki,
** who convinced us to get an Amiga in the first place and who also posted the
** code for the OpenInfoWindow routine used near the end of this program...
*/
 
#include <exec/types.h>
#include <intuition/intuition.h>
#include <graphics/gfxmacros.h>
#include <stdio.h>

/* Default values. Actually there are some more values in the program
** that should be #define'd here, such as the default speed, default number
** of disks, etc, but then I would have to have my strings work more
** dynamically, and, and, and... (excuses excuses)
*/
#define MAXDISKS            25
#define WIDTH              180
#define HEIGHT              60
#define WINDOWX    (625-WIDTH)
#define WINDOWY             14
#define TEXTDELAY           6L           /* Time to wait after text */
#define MINWIDTHFORTEXT  (4 + 8 * 7)     /* 8: Font Width, 7: #chars */

FILE *fopen(), *input, *output, temp;    /* For fputs/fgets console I/O. */

char *fgets();
void  fputs();

void *OpenLibrary(), *OpenWindow(), *FindTask(), *IntuitionBase, *GfxBase;

BYTE disks[3][MAXDISKS+1];  /* We have 3 towers with upto MAXDISKS disks  */
int  topdisk[3];            /* Free location on each tower (0 = all free) */
int  towerloc[4];           /* Pixel location of each tower (4th = imag.) */

/* Tons of globals... */
int dx, dy, height, width, top, moveto, 
    numsteps, numdisks, wbench, writetext;
long movedelay, windowcolor, diskcolor, 
     outlinecolor, beepcolor, textcolor, priority;

struct NewWindow WindowInfo =
{ WINDOWX, WINDOWY, WIDTH, HEIGHT,         /* Left, Top, Width, Height */
  0, 0,                    /* Detail pen, Block pen (-1 = use screens) */
  CLOSEWINDOW | NEWSIZE | REFRESHWINDOW | SIZEVERIFY,    /* IDCMPflags */
  WINDOWDEPTH | WINDOWSIZING |
  SMART_REFRESH | WINDOWCLOSE | WINDOWDRAG,                   /* Flags */
  NULL, NULL, NULL,              /* FirstGadget, Menu Checkmark, Title */
  NULL, NULL,                                        /* Screen, Bitmap */
  0, 0, 640, 200,                 /* Min Width/Height Max Width/Height */
  WBENCHSCREEN                                                 /* Type */
};

/* Structure to hold all the window info maintained by Intuition */
struct Window   *HanoiWindow;
struct RastPort *HanoiRPort;

/* A macro to make dealing with undefined argv's easier... */
#define ARGV(n) (argc <= n ? "" : argv[n])

main(argc, argv)
int   argc;
char *argv[];
{ 
  int i, cnt, GetNum(), GetArg();
  struct Task *thistask;

  if (wbench = (argc == 0)) {   /* Ie, started from the workbench */
    if (!OpenInfoWindow()) exit (1);
    fputs ("Enter parameters, <CR> or ^\\ for defaults\n", output);
    numdisks    = GetNum ("Number of disks? [1..25, default 5] ", 1, 25, 5);
    movedelay   = 12-GetNum ("Speed? [1..12, default 4]           ", 1, 12, 4);
    windowcolor = GetNum ("Background Color? [0..3, default 3] ", 0, 3, 3);
    textcolor   = GetNum ("Text Color? [0..3, default 3]       ", 0, 3, 3);
    diskcolor   = GetNum ("Disk Color [0..3, default 2]        ", 0, 3, 2);
    outlinecolor= GetNum ("Outline Color [0..3, default 2]     ", 0, 3, 2);
    priority    = -20L;
    CloseInfoWindow ();
  } else {    /* Started from the CLI */
    numdisks    = GetArg (ARGV(1), 1, 25, 5);
    movedelay   = 12-GetArg (ARGV(2), 1, 12, 4);
    windowcolor = GetArg (ARGV(3), 0, 3, 3);
    textcolor   = GetArg (ARGV(4), 0, 3, 3);
    diskcolor   = GetArg (ARGV(5), 0, 3, 2);
    outlinecolor= GetArg (ARGV(6), 0, 3, 2);
    WindowInfo.LeftEdge = GetArg (ARGV(7), 0, 640 - WIDTH,  WINDOWX);
    WindowInfo.TopEdge  = GetArg (ARGV(8), 0, 200 - HEIGHT, WINDOWY);
    priority    = GetArg (ARGV(9), 0, 8, 0) * 5 - 20;
  };

  if (thistask = FindTask (NULL)) SetTaskPri (thistask, (long)priority);

  numsteps             = (numdisks * 2) + 4;
  writetext            = (windowcolor != textcolor);
  WindowInfo.DetailPen = windowcolor;
  WindowInfo.BlockPen  = windowcolor;
  WindowInfo.MinWidth  = numsteps * 3;
  WindowInfo.MinHeight = numdisks * 2 + 4 + writetext * 10;
  WindowInfo.Height   += (writetext * 10);
  /* Make sure beepcolor is different than disk or window color */
  if ((beepcolor = ((diskcolor - 1) & 3)) == windowcolor)
     beepcolor = (beepcolor - 1) & 3;

  OpenLibraries();  
  SetupEnvironment();  
  ReCalculateParameters();
  InitializeTowers();  
  ClearWindow();
  DrawAllDisks(diskcolor);

  for (cnt = 0; cnt < 10; cnt++) {
    CheckForMessages ();  Delay (20L);
  }
  while (1) 
    for (i = 0; i < 3; i++) {
      MoveDisks(i, (i+1)%3, (i+2)%3, numdisks);  
      DrawAllDisks (beepcolor); Delay (6L); DrawAllDisks (diskcolor);
      for (cnt = 0; cnt < numdisks * 3; cnt++) {
        CheckForMessages ();  Delay (20L);
      }
    }
}

/* Called after resizing to determine new parameters...
*/
ReCalculateParameters()
{
  int xstart, i;

  top    = (writetext ? 9 : 0);
  height = HanoiWindow->Height;
  width  = HanoiWindow->Width;
  dx     = width / (3 * numsteps);
  dy     = (height-top) / (numdisks + 2);
  xstart = (width - dx * 3 * numsteps) / 2;
  for (i = 0; i < 4; i++) towerloc[i] = xstart + i * numsteps * dx;
  moveto = top + dy;
}

DrawDiskAtLoc (disk, tower, location, diskcolor, outlinecolor)
int  disk, tower, location;
long diskcolor, outlinecolor;
{
  if (disk) {
    SetAPen (HanoiRPort, diskcolor);
    SetOPen (HanoiRPort, outlinecolor);
    RectFill (HanoiRPort, 
              (long)(towerloc[tower] + (numdisks - disk + 1) * dx),
              (long)(height - (location + 1) * dy),
              (long)(towerloc[tower+1] - 1 - (numdisks - disk + 1) * dx),
              (long)(height - (location * dy) - 2)); 
  }
}
            
DrawAllDisks(color)
long color;
{
  int tower, location;

  for (tower = 0; tower < 3; tower++) 
    for (location = 0; location < numdisks; location++)
      DrawDiskAtLoc (disks [tower][location], tower,
                     location, color, outlinecolor);
}
  

/* InitializeTowers will clear towers 1 and 2 and fill up tower 0. 
** To start off, we have disk[0][0] = largest disk, disk[0][1] = next
*/
InitializeTowers()
{
  int i;
  for (i=0; i<numdisks; i++) {
    disks[0][i] = numdisks-i; 
    disks[1][i] = 0;
    disks[2][i] = 0;
  }
  topdisk[0] = numdisks;
  topdisk[1] = 0;
  topdisk[2] = 0;
}

/* Write over *everything* in sight, including the borders and the title...
*/
ClearWindow ()
{
  SetOPen  (HanoiRPort, windowcolor);
  SetAPen  (HanoiRPort, windowcolor);
  RectFill (HanoiRPort, 0L, 0L, (long)(width-1), (long)(height-1));
}

static char titlestring[8] =  "X->X   ";

/* ShowMoveInfo will print out the current move being made. Note that
** this routine is NOT CALLED at all if the user specifies the text color
** to be the same as the window color... This routine does slow down the
** program, but it makes it a more educational tool...
*/
ShowMoveInfo (fromtower, totower, ndisks)
int fromtower, totower, ndisks;
{
  if (ndisks > 9) titlestring[5] = '0' + ndisks / 10;
  else titlestring[5] = ' ';
  titlestring[6] = '0' + ndisks % 10;
  titlestring[0] = 'A' + fromtower;
  titlestring[3] = 'A' + totower;

  SetAPen (HanoiRPort, textcolor);
  SetDrMd (HanoiRPort, JAM2);
  Move (HanoiRPort, 1L, 8L);
  Text (HanoiRPort, "       ", 7L);
  SetDrMd (HanoiRPort, JAM1);
  Move (HanoiRPort, 1L, 8L);
  Text (HanoiRPort, titlestring, 7L);
}  
      

/* This is the recursive Hanoi function! Looks simple, no?
*/
MoveDisks (fromtower, totower, sparetower, ndisks)
int fromtower, totower, sparetower, ndisks;
{
  if (writetext && width > MINWIDTHFORTEXT) {
    ShowMoveInfo (fromtower, totower, ndisks);
    Delay (TEXTDELAY * movedelay);
    CheckForMessages ();
  }

  if (ndisks == 1)  /* Base case */
    MoveOneDisk (fromtower, totower);
  else {
    MoveDisks   (fromtower, sparetower, totower, ndisks-1);
    MoveOneDisk (fromtower, totower);
    MoveDisks   (sparetower, totower, fromtower, ndisks-1);
  }
}

/* This moves (graphically) the top disk from fromtower to totower.
*/
MoveOneDisk (fromtower, totower)
int fromtower, totower;
{
  int cnt;
  int disk = disks[fromtower][topdisk[fromtower]-1];

  if (writetext && width > MINWIDTHFORTEXT) 
    ShowMoveInfo (fromtower, totower, 1);

  for (cnt = topdisk[fromtower]; cnt <= numdisks; cnt++) {
    DrawDiskAtLoc (disk, fromtower, cnt-1, windowcolor, windowcolor);
    DrawDiskAtLoc (disk, fromtower, cnt, diskcolor, outlinecolor);
    Delay (movedelay);
  }

  topdisk[fromtower]--;
  disks[fromtower][topdisk[fromtower]] = 0;
  CheckForMessages ();

  if (fromtower < totower) 
    for (cnt = fromtower+1; cnt <= totower; cnt++) {
      DrawDiskAtLoc (disk, cnt-1, numdisks, windowcolor, windowcolor);
      DrawDiskAtLoc (disk, cnt, numdisks, diskcolor, outlinecolor);
      Delay (movedelay);
    }
  else 
    for (cnt = fromtower-1; cnt >= totower; cnt--) {
      DrawDiskAtLoc (disk, cnt+1, numdisks, windowcolor, windowcolor);
      DrawDiskAtLoc (disk, cnt, numdisks, diskcolor, outlinecolor);
      Delay (movedelay);
    };

  CheckForMessages ();

  for (cnt = numdisks-1; cnt >= topdisk[totower]; cnt--) {
    DrawDiskAtLoc (disk, totower, cnt+1, windowcolor, windowcolor);
    DrawDiskAtLoc (disk, totower, cnt, diskcolor, outlinecolor);
    Delay (movedelay);
  }

  disks[totower][topdisk[totower]] = disk;
  topdisk[totower]++;

  CheckForMessages ();  
}
 
CheckForMessages ()
{
  struct IntuiMessage *message, *GetMsg();
  ULONG  class;  /* What kind of message? */

  /* Try to get a message. If there is one, process it. */  

  while (message = GetMsg(HanoiWindow->UserPort)) {
    class = message->Class;
    ReplyMsg(message);
    switch (class) {
	case SIZEVERIFY    : Wait (1L << HanoiWindow->UserPort->mp_SigBit);
			     break;
        case NEWSIZE       : ReCalculateParameters();  /* Fall through */
        case REFRESHWINDOW : ClearWindow(); 
                             DrawAllDisks(diskcolor);
  			     break;
        case CLOSEWINDOW   : CloseThings(0);  /* CloseThings exits */
        default            : CloseThings(1);  /* CloseThings exits */
    }
  }  
}

OpenLibraries ()
{
  if (!(IntuitionBase = OpenLibrary("intuition.library",0L))) 
    Panic("No intuition library\n");
  if (!(GfxBase = OpenLibrary("graphics.library",0L))) 
    Panic("No graphics library\n");
}  

/* Initializes the Hanoi window and sets up the HanoiRPort, used often. */
SetupEnvironment()
{
  if (!(HanoiWindow = OpenWindow(&WindowInfo))) Panic ("Can't open window\n");
  HanoiRPort = HanoiWindow->RPort;
  SetBPen (HanoiRPort, windowcolor);
}

/* Prints out an error message about what went wrong and exits gracefully */
Panic(errormsg)
char *errormsg;
{
  if (wbench) DisplayBeep (NULL);   /* Simply flash the display... */
  else puts (errormsg);
  CloseThings(1);
}

/* Closes the Libraries that have been opened and frees up all memory */ 
CloseThings(error_code)
int error_code;
{ 
  if (HanoiWindow)   CloseWindow(HanoiWindow);
  if (GfxBase)       CloseLibrary(GfxBase);
  if (IntuitionBase) CloseLibrary(IntuitionBase);
  exit (error_code);
}

int GetNum (prompt, min, max, def)
int min, max, def;
char *prompt;
{
  int   result, cnt;
  char  resp[80];

  while (1) {
    fputs (prompt, output); fflush (output);
    fgets (resp, 80, input);
    cnt = 0;
    while (resp[cnt] == ' ') cnt++;
    if (resp[cnt] == '\0' || resp[cnt] == '\n') return (def);
    result = 0;
    while (resp[cnt] != '\0' && resp[cnt] != '\n' &&
           result <= max && result != -1) {
      if (resp[cnt] < '0' || resp[cnt] > '9') result = -1;
      else result = result * 10 + resp[cnt++] - '0';
    };
    if (result >= min && result <= max) return (result);
    if (result == -1) fputs ("Illegal integer, please try again.\n", output);
    else fputs ("Value out of range, please try again.\n", output);
  }
}

/* Opens a console window for fputs/fgets compatible I/O. Thanks to Tom
** Rokicki (from whose posting the last 4 lines of this routine come...).
*/
int OpenInfoWindow ()
{
   static char consp[] = "CON:30/30/400/80/Hanoi by Ali Ozer";
   consp[30] = 214; /* Guess what this does? Run the program and see */

   if ((output = fopen (consp, "w+")) == NULL) return (0);     
   input = &temp;
   *input = *output;
   return (1);
}

CloseInfoWindow ()
{
  fclose (input);
  fclose (output);
}

/* Gets a positive integer from argstr. If argstr is empty or "-", return
** defaultnum. Error if argstr is an invalid number or is not in range.
*/
int GetArg (argstr, min, max, def)
int min, max, def;
char *argstr;
{
  int result = 0;
  if (strlen(argstr) == 0 || !strcmp(argstr,"-")) return (def);
  while (*argstr != '\0' && result <= max) {
    if (*argstr < '0' || *argstr > '9') IllegalArgs();
    result = result * 10 + *argstr++ - '0';
  };
  if (result >= min && result <= max) return (result);
  IllegalArgs ();
}

/* Prints usage line and causes program to terminate. Considering we only
** call GetArg once we know we were called from CLI, we can safely do puts.
*/
IllegalArgs ()
{
  puts ("\nUsage:\nHanoi NDisks Speed BGColor TxtColor DskColor OutLineColor X Y Priority");
  puts (" NDisks 1..25, Speed 0..12, Colors 0..3 (WorkBench colors)");
  puts (" Priority 0..8 (Corresponding to -20..+20)");
  puts (" All arguments are optional. Use - to get a default value.\n");
  CloseThings (1);
}      

/* hanoi.c, by Ali Ozer */
