File:  [OS/2 SDKs] / os232sdk / toolkt20 / c / samples / hanoi / hanoi.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Thu Aug 9 12:26:30 2018 UTC (7 years, 9 months ago) by root
Branches: msft, MAIN
CVS tags: os2sdk-1990, HEAD
Microsoft OS/2 SDK 2.0 05-30-1990

/*************************************************************

 This program implements a tower of hanoi program.  This
 sample app was written to demonstrate the use of a multi-
 threaded program.  The main thread handles the PM interface,
 the second thread is started to do the recursive execution
 of the hanoi algorithm.

 Created by Microsoft, IBM Corporation 1990

 DISCLAIMER OF WARRANTIES.  The following [enclosed] code is 
 sample code created by Microsoft Corporation and/or IBM 
 Corporation. This sample code is not part of any standard 
 Microsoft or IBM product and is provided to you solely for 
 the purpose of assisting you in the development of your 
 applications.  The code is provided "AS IS", without 
 warranty of any kind.  Neither Microsoft nor IBM shall be 
 liable for any damages arising out of your use of the sample 
 code, even if they have been advised of the possibility of 
 such damages.

 Procedures in this file:
   main()             Sets up the PM environment and heap and
                      calls the main window procedure ClientWndProc
   ClientWndProc()    Handles the main window messages
   CalcThread()       Sets up and terminates the secondary thread
   DrawDisk()         Draws or erases a disk on one of the poles
   MoveDisk()         Moves a disk from one pole to another
   Hanoi()            Recursive alogrithm for tower of hanoi prog
   EnableMenuItem()   Activates/deactivates a menu choice
   EntryFldDlgProc()  Handles the set number of disks dialog box
   SetupTowers()      Sets up the global tower data

**************************************************************/

#include "hanoi.h"

/********************* GLOBALS *******************************/

CHAR szClientClass[] = "Hanoi";

/* Note that this use of global data precludes multiple windows
   of hanoi running at the same time.  Thus, from an object-
   oriented perspective, this is less than desireable and the
   data should be passed into the window, rather than used
   explicitly. */

BYTE    abTowers[3][MAXDISKCNT];     /* Used to hold disk numbers on each post */
BYTE    abTowersNdx[3];              /* Current number of disks on each post   */
BYTE    cTowerSize = DEFAULTSIZE;   /* Holds the total number of disks        */
USHORT  ausPolePos[3]= { POSTOFFSET, /* Holds offset drawing information     */
                       POSTOFFSET + POSTSPACE,
                       POSTOFFSET + 2*POSTSPACE };
ULONG   ulIterations;
TID     tidCalcThread;
BOOL    fContinueCalc;
HWND    hwndFrame, hwndClient;


/*************************************************************/


/*
 * Function name: main()
 *
 * Parameters:  argc, argv.  If the user places a number (1 thru MAXDISKCNT)
 *              on the command line, that number will become the default
 *              number of disks on the stack. Otherwise there will be
 *              DEFUALTSIZE disks initially.
 *
 * Returns: Always returns 0
 *
 * Purpose: Parses the command line, sets up the PM environment, prepares
 *          the Tower arrays, calls the main window proc, handles the
 *          window's messages then cleans up and exits.
 *
 * Usage/Warnings:
 *
 * Calls: ClientWndProc() (thru PM)
 */

int main(int argc, char *argv[])
{

   HAB          hab;
   HMQ          hmq;
   QMSG         qmsg;
   
   ULONG flFramefContinueCalcFlags = FCF_TITLEBAR      | FCF_SYSMENU | FCF_MINBUTTON |
                        FCF_SHELLPOSITION | FCF_MENU    | FCF_TASKLIST  |
                        FCF_ICON          | FCF_BORDER  | FCF_ACCELTABLE;

   SHORT  sHold;

   if(argc > 1)  /* If command line arg, use as the initial number of disks */
   {
      sHold = atoi(argv[1]);
      if(sHold>0 && sHold<=MAXDISKCNT)
         cTowerSize = (BYTE) sHold;
   }
   SetupTowers();

   /* These PM calls should be error checked */
   hab = WinInitialize(0);
   hmq = WinCreateMsgQueue(hab, 0);

   if(!WinRegisterClass(hab, szClientClass,(PFNWP)ClientWndProc,0L,0))
   {
      WinAlarm(HWND_DESKTOP, WA_ERROR);        /* Register failed */
      DosExit(EXIT_PROCESS,1);
   }

   hwndFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE,
                                  &flFramefContinueCalcFlags, szClientClass, NULL,
                                  0L, NULL, ID_RESOURCE, &hwndClient);
   if(!hwndFrame)
   {
      WinAlarm(HWND_DESKTOP, WA_ERROR); /* Window create failed */
      DosExit(EXIT_PROCESS,1);
   }

   while(WinGetMsg(hab,&qmsg,NULL,0,0))        /* Message loop */
      WinDispatchMsg(hab,&qmsg);

   WinDestroyWindow(hwndFrame);                /* Clean up     */
   WinDestroyMsgQueue(hmq);
   WinTerminate(hab);
   return 0;
}


/*
 * Function name: ClientWndProc()
 *
 * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Window Proc params.
 *              No user data is expected in the WM_CREATE.
 *
 * Returns:
 *
 * Purpose: Handles all the messages associated with the main window
 *          and calls the appropriate handling procedures.
 *
 * Usage/Warnings: Called only by main().  Note that when WM_PAINT executes,
 *                 the secondary thread may change data during the update
 *                 which may cause a problem.  However, this is NOT a write
 *                 conflict, as only 1 thread does the writing.
 *
 * Calls:  DrawDisk(), CalcThread() (thru Thread), EntryFldDlgProc() (thru PM)
 */

MRESULT EXPENTRY ClientWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
{
   HPS    hps;                         /* Handle for painting           */
   RECTL  rcl;                         /* Rectangle struct for painting */
   POINTL ptl;                         /* Point struct for painting     */
   BYTE   cPole,bCnt,bHeight,cnt;      /* Utility variables             */
   CHAR   szMsg[MSGBUFSIZE];           /* sprintf buffer                */

   switch(msg)
   {
      case WM_PAINT:
         hps = WinBeginPaint(hwnd,NULL,NULL);   /* Get paint handle     */
         WinQueryWindowRect(hwnd,&rcl);

         DrawRect(rcl.xLeft,rcl.yBottom,        /* White out the screen */
                  rcl.xRight,rcl.yTop,CLR_WHITE);

         /* Draw the base */
         DrawRect(BASEXOFFSET,           BASEYOFFSET,
                  BASEXOFFSET+BASELEN-1, BASEYOFFSET+BASETHICK-1,
                  CLR_DARKGREEN);

         /* Draw the 3 posts */
         bHeight = cTowerSize*DISKSPACE + POSTEXTRAHT;
         for(cnt=0;cnt<3;cnt++)
         {
            DrawRect(ausPolePos[cnt]-POSTHALF,          BASEYOFFSET,
                     ausPolePos[cnt]-POSTHALF+POSTWIDTH,bHeight,
                     CLR_DARKGREEN);
         }

         /* Place the appropriate disks on each pole */
         for(cPole=0;cPole<3;cPole++)
         {
            for(bCnt=0;bCnt<abTowersNdx[cPole];bCnt++)
            {
               DrawDisk(hps,cPole,bCnt,fDRAW);
            }
         }

         WinEndPaint(hps);
         return 0L;

      case WM_COMMAND:
         switch(SHORT1FROMMP(mp1))
         {
            case IDM_START:
                          /* Set continuation fContinueCalc  */
               fContinueCalc = TRUE;
               ulIterations = 0;         /* Zero iteration counter */

               if (DosCreateThread (&tidCalcThread, CalcThread,
                                         (ULONG)NULL, 0L, STACK))
               {
                  DosBeep (440, 175);
                  return 0L;
               }

               /* Disallow menu items that could change data while the second
                  thread is running */
               EnableMenuItem(hwnd,IDM_START,FALSE); /* Disable Start & set */
               EnableMenuItem(hwnd,IDM_SET,FALSE);
               EnableMenuItem(hwnd,IDM_STOP,TRUE);   /* Enable Stop item    */
               return 0L;

            case IDM_STOP:
               fContinueCalc = FALSE;  /* Notify thread to quit          */
               return 0L;

            case IDM_SET:
               if(WinDlgBox(HWND_DESKTOP, hwnd, /* Pop up the query/set box */
                         (PFNWP)EntryFldDlgProc,NULL,ID_SETCOUNT,NULL))
               {
                  SetupTowers();                          /* Reset towers   */
                  WinInvalidateRect(hwnd,NULL,FALSE);     /* Force a redraw */
               }
               return 0L;
               break;

            case IDM_ABOUT:
                WinDlgBox(HWND_DESKTOP,
                          hwnd,
                          (PFNWP)AboutDlgProc,
                          NULL,
                          IDD_ABOUTBOX,
                          NULL);
                break;


             default:
                return WinDefWindowProc(hwnd, msg, mp1, mp2);
         }

      case UM_CALC_DONE:
         EnableMenuItem(hwnd,IDM_START,TRUE);  /* Reenable Start & set      */
         EnableMenuItem(hwnd,IDM_SET,TRUE);
         EnableMenuItem(hwnd,IDM_STOP,FALSE);  /* Disable stop              */

         sprintf(szMsg,"%lu disks were moved.",ulIterations);  /* Print msg */
         WinMessageBox(HWND_DESKTOP, hwnd, szMsg, "Done!", 0, MB_OK);

         SetupTowers();                        /* Reset towers              */
         WinInvalidateRect(hwnd,NULL,FALSE);   /* Force a screen redraw     */
         return 0L;

       default:
          return WinDefWindowProc(hwnd, msg, mp1, mp2);
   }
}


/*
 * Function name: CalcThread()
 *
 * Parameters: none 
 *
 * Returns: VOID
 *
 * Purpose: Calls the recursive Hanoi with initial setting of 0,2,1 meaning
 *          from pole 0, to pole 2, using pole 1 as a temporary.  Hanoi
 *          returns when finished, or the user says stop.  This proc then
 *          sets a critical section so the posted message won't be handled
 *          until the thread is terminated.
 *
 * Usage/Warnings: No DosExitCritSec() is called since _endthread() supposedly
 *                 clears the critical section when the thread is
 *                 terminated.
 *
 * Calls:  Hanoi()
 */

VOID CalcThread ()
{
   HAB hab;

   hab = WinInitialize(0);    /* Called to increase Ring 2 stack size */
   Hanoi(cTowerSize,0,2,1);      /* Execute the recursive routine */
   WinTerminate(hab);

   DosEnterCritSec(); /* Set Crit so the UM_CALC_DONE isn't processed */
                      /* until this thread has completely terminated  */
   WinPostMsg(hwndClient,UM_CALC_DONE,NULL,NULL);         /* Post done */

   DosExit (EXIT_THREAD, 0);                      /* Terminate thread */
}


/*
 * Function name: DrawDisk()
 *
 * Parameters:  hps is a handle to the main PS space.
 *              cPole is the pole (0-2) to draw the disk on.
 *              bLevel is the number of spaces from the bottom to draw disk.
 *              fDraw if =0, erase disk, if =1 draw disk.
 *
 * Returns: VOID
 *
 * Purpose: This routine takes a PS handle, the hanoi spindle and disk level
 *          and draws an appropriately sized disk.
 *
 * Usage/Warnings: Does not grab exclusive access to the screen before
 *                 drawing which may cause a problem.
 *
 * Calls:
 */

VOID DrawDisk(HPS hps,BYTE cPole, BYTE bLevel,BYTE fDraw)
{
   USHORT usXstart,usYstart,usWidth;
   POINTL ptl;

   usYstart = BOTDISKYPOS + DISKSPACE*bLevel;  /* Calculate Bottom of disk   */

   usWidth  = (MAXDISKWIDTH-MINDISKWIDTH)*abTowers[cPole][bLevel]/cTowerSize
              + MINDISKWIDTH;                  /* Calculate the disk's width */

   usXstart = ausPolePos[cPole] - usWidth/2;   /* Center disk on pole        */

   if(fDraw == fDRAW)  /* If we are to draw the disk */
   {
      DrawRect(usXstart,usYstart,usXstart+usWidth,
               usYstart+DISKTHICK-1,CLR_RED);
   }
   else         /* We are to erase the disk, then redraw the pole */
   {
      DrawRect(usXstart,usYstart,usXstart+usWidth,
               usYstart+DISKTHICK-1,CLR_WHITE);
      DrawRect(ausPolePos[cPole]-POSTHALF,usYstart,
               ausPolePos[cPole]-POSTHALF+POSTWIDTH,usYstart+DISKTHICK-1,
               CLR_DARKGREEN);
   }
}


/*
 * Function name: MoveDisk()
 *
 * Parameters:  hps is a handle to the main PS space.
 *              bFrom is the spindle to take the top disk from.
 *              bTo is the spindle to place the disk on.
 *
 * Returns: VOID
 *
 * Purpose: This routine moves the top disk from the bFrom spindle to the top
 *          of the bTo spindle.
 *
 * Usage/Warnings: Does error checking for trying to move a disk from a
 *                 spindle that does not have any disks on it.
 *
 * Calls:  MoveDisk()
 */

VOID MoveDisk(HPS hps,BYTE bFrom,BYTE bTo)
{
   CHAR bTOSndx;  /* Top of stack index  */
   BYTE bDiskNum; /* Disk number to move */

   bTOSndx = abTowersNdx[bFrom]-1;
   if(bTOSndx < 0)
      return;

   DrawDisk(hps,bFrom,bTOSndx,fERASE);   /* Remove disk off from stack     */

   bDiskNum = abTowers[bFrom][bTOSndx];  /* Get move disk number           */
   abTowersNdx[bFrom]--;                 /* Decrease count on from spindle */

   bTOSndx = abTowersNdx[bTo]++;         /* Offset to place the disk at    */
   abTowers[bTo][bTOSndx] = bDiskNum;    /* Place on stack in memory       */

   DrawDisk(hps,bTo,bTOSndx,fDRAW);      /* Draw disk on the to stack      */
}


/*
 * Function name: Hanoi()
 *
 * Parameters:  pcp is a struct which contains the hwnd handle and the
 *                  continue fContinueCalcFlag which is initially set to TRUE.
 *              bHeight is the number of disks in the from stack to move.
 *              bFrom is the from spindle number, 0-2.
 *              bTo is the to spindle number.
 *              bTemp is the temporary spindle number.
 *
 * Returns: VOID
 *
 * Purpose: This routine implements a recursive hanoi program that works as
 *          follows:  By recursion, move all the disks, except for the
 *          bottom disk to the temporary stack.  Then move the bottom
 *          disk to the target spindle.  Now recursively move the stack
 *          on the temporary spindle to the target spindle.  The limiting
 *          case is when Hanoi() is called with a bHeight of 0 in which
 *          case the depth recursion is terminated.
 *
 * Usage/Warnings: This routine checks the ->fContClac fContinueCalcFlag, which is set
 *                 by the main thread when the user selects stop, to see if
 *                 the user wishes to abort the algorithm.  If so, it backs
 *                 out and exits.
 *
 * Calls:  MoveDisk()
 */

VOID Hanoi(BYTE bHeight, BYTE bFrom, BYTE bTo, BYTE bTemp)
{
   HPS hps;

   if(bHeight<=0 || !fContinueCalc)       /* Exit up if no more disks or */
      return;                             /* the user said Stop          */

   Hanoi(bHeight-1,bFrom,bTemp,bTo);  /* Move all but bottom disk    */

   if(!fContinueCalc)                /* If user said to stop        */
      return;

   /* Display bFrom -> bTo */
   hps = WinGetPS(hwndClient);
   MoveDisk(hps,bFrom,bTo);               /* Move the bottom disk        */
   WinReleasePS(hps);
   ulIterations++;

   Hanoi(bHeight-1,bTemp,bTo,bFrom);  /* Move disks over             */
}


/*
 * Function name: EnableMenuItem()
 *
 * Parameters:  hwnd is a handle of the current window.
 *              sMenuItem is the ID of the item to Enable/Disable.
 *              fEnable will enable item if TRUE, otherwise disables it.
 *
 * Returns: VOID
 *
 * Purpose: This routine handles enabling/disabling of menu items.  This
 *          is done by getting Parent and Menu hwnd handles then sending
 *          the appropriate message.
 *
 * Usage/Warnings:
 *
 * Calls:
 */

VOID EnableMenuItem(HWND hwnd, SHORT sMenuItem, BOOL fEnable)
{
   HWND hwndParent = WinQueryWindow(hwnd, QW_PARENT, FALSE);
   HWND hwndMenu   = WinWindowFromID(hwndParent, FID_MENU);

   WinSendMsg(hwndMenu, MM_SETITEMATTR,
              MPFROM2SHORT(sMenuItem, TRUE),
              MPFROM2SHORT(MIA_DISABLED, fEnable ? 0 : MIA_DISABLED));
}


/*
 * Function name: EntryFldDlgProc()
 *
 * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
 *              No user data is expected in the WM_CREATE.
 *
 * Returns: Terminates with a TRUE iff a new valid Tower Size has been entered.
 *
 * Purpose: Handles all the messages associated with the set entry field
 *          and calls the appropriate handling procedures.  The purpose
 *          of this dialog box is to get a new number of disks for the
 *          hanoi routine.
 *
 *
 * Usage/Warnings: If the value entered is valid, global cTowerSize is
 *                 changed to the new value, and TRUE is returned.
 *
 * Calls:
 */

MRESULT EXPENTRY EntryFldDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
{
   SHORT sNewSize;            /* Holds new number of disks        */

   switch(msg)
   {
      case WM_INITDLG:
         WinSendDlgItemMsg(hwnd, ID_ENTRYFLD,EM_SETTEXTLIMIT,  /* Limit len */
                                 MPFROM2SHORT(2,0),NULL);
         WinSetDlgItemShort(hwnd, ID_ENTRYFLD,(SHORT) cTowerSize,TRUE);
         return 0L;                           /* Allow normal focus setting */

      case WM_COMMAND:
         switch(SHORT1FROMMP(mp1))
         {
            case DID_OK:
               WinQueryDlgItemShort(hwnd, ID_ENTRYFLD,
                                    &sNewSize, TRUE); /* Get the short      */
               if(sNewSize>0 && sNewSize<=MAXDISKCNT) /* Set new Tower size */
               {
                  cTowerSize = (BYTE) sNewSize;
                  WinDismissDlg(hwnd,TRUE);
               }
               else                                   /* Invalid value      */
               {
                   WinMessageBox (HWND_DESKTOP, hwndFrame, "You may only select up to 16 disks",
                                  "OOPS! Too many disks!", ID_MSGBOX, MB_OK |
                                   MB_ICONEXCLAMATION);
               }

               return 0L;

            case DID_CANCEL:
               WinDismissDlg(hwnd,FALSE);
               return 0L;

            default:
               return WinDefDlgProc(hwnd, msg, mp1, mp2);
         }

      default:
         return WinDefDlgProc(hwnd, msg, mp1, mp2);
   }
}

/*
 * Function name: AboutDlgProc()
 *
 * Parameters:  hwnd, msg, mp1, mp2.  Standard PM Dialog Proc params.
 *              No user data is expected in the WM_CREATE.
 *
 * Returns:
 *
 * Purpose: Handles all the messages associated with the About Box
 *
 *
 * Usage/Warnings:
 *
 * Calls:
 */

MRESULT EXPENTRY AboutDlgProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
{

   switch(msg)
   {
      case WM_COMMAND:
         WinDismissDlg(hwnd, TRUE);
         break;

      default:
         return WinDefDlgProc(hwnd, msg, mp1, mp2);
   }
}

/*
 * Function name: SetupTowers()
 *
 * Parameters:  None
 *
 * Returns: VOID
 *
 * Purpose: This routine initializes the global arrays that represent the
 *          hanoi stacks.  This involves placing all the disks on the
 *          first peg, emptying the other 2 pegs and setting the associated
 *          counts.
 *
 * Usage/Warnings: Calling uses the global variable cTowerSize to determine
 *                 how many disks there are.
 *
 * Calls:
 */

VOID SetupTowers()
{
   USHORT cnt;

   for(cnt=0;cnt<cTowerSize;cnt++)       /* Setup the intial post with disks */
      abTowers[0][cnt] = cTowerSize-cnt-1;

   abTowersNdx[0] = cTowerSize;          /* Set disk count for initial post  */
   abTowersNdx[1] = abTowersNdx[2] = 0;  /* Zero other post counts           */
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.