[comp.sys.amiga] Towers of Hanoi

ali@navajo.UUCP (02/21/87)

----->8 cut cut --------

Here's a fun little program. It solves the towers of hanoi in a workbench
window of its own... It occupies little space, and is meant to be run
with a low priority (by default, -20). I was inspired by a similar program
I saw running on a workstation once. (But this is better (of course),
because it has color and runs on our favorite micro.) 

The program will go on solving the puzzle forever, unless stopped by clicking
on the (invisible) close gadget. The window also provides sizing and depth
gadgets, all also invisible. You can vary the parameters like speed at which
the disks are moved, the number of disks (1..25), colors, etc... From CLI,
you can specify these parameters as command line arguments (type "hanoi ?"
to see what is expected). From the Workbench, the program opens a little
window and asks you for the values... 

Using the default values (5 disks, moving pretty slowly, and no text),
and a stack size of 2000, I was able to get 22 hanoi's running (with 512k
memory). Try it if you've got nothing better to do --- it's real fun!
The overall speed at which the puzzle is solved doesn't change, really.
(But, let me warn you, starting up 22 hanois will take some time... By the
last few Intuition is really having a fun time drawing and redrawing all
of the windows, over and over, every time to activate or reactive or resize
any window...)

I compiled hanoi with Manx, using 16 bits (code is about 9.2k). It should
compile under Lattice, but that's what I said about YaBoing, too... (and
it didn't)

I'll appreciate any comments or suggestions! (But please don't tell me the
disks look too ugly --- I know they do. Oh well! Not much you can do with
RectFill(), you know.)

Ali Ozer, ali@score.stanford.edu OR ali@navajo.stanford.edu

------->8 cut here and compile! you might also want to design an icon -----

/* 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 */

dave@uwmcsd1.UUCP (02/24/87)

> ----->8 cut cut --------
> 
> Here's a fun little program. It solves the towers of hanoi in a workbench
> 
> I compiled hanoi with Manx, using 16 bits (code is about 9.2k). It should
> compile under Lattice, but that's what I said about YaBoing, too... (and
> it didn't)
> 
I compiled it with Lattice 3.03. It didn't like the 'min' and 'max'
scattered through the code which I changed to zmin, zmax (or use your
imagination). Lattice also doesn't appear to have 'output' defined as
stdout, so I just converted the first fputs() to a puts().

Anyhow, it is an amusing little thing to keep your amiga busy while
you're away...

-- 
#include <std-disclaimer.h>
Dave Rasmussen c/o Computing Services Division @ U of WI - Milwaukee mmm
Internet: dave@csd1.milw.wisc.edu  Uucp:  uwvax!uwmcsd1!dave        {o,o}
Csnet:	  dave%uwmcsd1@uwm	   Phone: +1 (414) 963-5133          \u/

dillon@CORY.BERKELEY.EDU.UUCP (02/25/87)

	The reason it didn't like min or max is because Lattice's STDIO.H 
#define's min and max as macros ( min(a,b) and max(a,b) ).

				-Matt