home *** CD-ROM | disk | FTP | other *** search
- /*************************************************************
-
- 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.
-
- This program was written by Jeff Johnson, 7/89.
-
- 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;
-
- /*************************************************************/
-
-
- /*
- * 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;
- HWND hwndFrame, hwndClient;
- QMSG qmsg;
-
- ULONG flFrameFlags = 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,ClientWndProc,0L,0))
- {
- WinAlarm(HWND_DESKTOP, WA_ERROR); /* Register failed */
- DosExit(EXIT_PROCESS,1);
- }
-
- hwndFrame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE,
- &flFrameFlags, szClientClass, NULL,
- 0L, (HMODULE) NULL, ID_MAINMENU, &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 */
- static CALCPARAM cp; /* Struct used to notify thread */
- static TID tidCalc; /* Secondary thread ID */
- static VOID *pThreadStack; /* Pointer to secondary stack */
-
- 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 = (BYTE) (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(COMMANDMSG(&msg)->cmd)
- {
- case IDM_START:
- /* Try to get stack space */
- if((pThreadStack = malloc(STACKSIZE)) == NULL)
- {
- WinAlarm(HWND_DESKTOP, WA_ERROR); /* Couldn't get memory */
- return 0L;
- }
- cp.hwnd = hwnd; /* Set the static struct */
- cp.fContinueCalc = TRUE;
- ulIterations = 0; /* Zero iteration counter */
-
- /* Try to start the thread */
- if((tidCalc = _beginthread(CalcThread,pThreadStack,
- STACKSIZE, &cp)) == -1)
- {
- free(pThreadStack); /* Thread wouldn't start */
- WinAlarm(HWND_DESKTOP, WA_ERROR);
- 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:
- cp.fContinueCalc = FALSE; /* Notify thread to quit */
- return 0L;
-
- case IDM_SET:
- if(WinDlgBox(HWND_DESKTOP, hwnd, /* Pop up the query/set box */
- EntryFldDlgProc,(HMODULE) NULL,ID_SETCOUNT,NULL))
- {
- SetupTowers(); /* Reset towers */
- WinInvalidateRect(hwnd,NULL,FALSE); /* Force a redraw */
- }
- return 0L;
-
- 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 */
- free(pThreadStack); /* Free thread's stack space */
-
- 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: pcp is a struct which contains the hwnd handle and the
- * continue flag which is initially set to TRUE.
- *
- * 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 _cdecl FAR CalcThread(PCALCPARAM pcp)
- {
- HAB hab;
-
- hab = WinInitialize(0); /* Called to increase Ring 2 stack size */
- Hanoi(pcp,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(pcp->hwnd,UM_CALC_DONE,NULL,NULL); /* Post done */
-
- _endthread(); /* 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 = (CHAR) (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 flag 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 ->fContinueCalc flag, 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(PCALCPARAM pcp, BYTE bHeight, BYTE bFrom, BYTE bTo, BYTE bTemp)
- {
- HPS hps;
-
- if(bHeight<=0 || !pcp->fContinueCalc) /* Exit up if no more disks or */
- return; /* the user said Stop */
-
- Hanoi(pcp,(BYTE)(bHeight-1),bFrom,bTemp,bTo); /* Move all but bottom disk */
-
- if(!pcp->fContinueCalc) /* If user said to stop */
- return;
-
- /* Display bFrom -> bTo */
- hps = WinGetPS(pcp->hwnd);
- MoveDisk(hps,bFrom,bTo); /* Move the bottom disk */
- WinReleasePS(hps);
- ulIterations++;
-
- Hanoi(pcp,(BYTE)(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(COMMANDMSG(&msg)->cmd)
- {
- 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 */
- WinDismissDlg(hwnd,FALSE);
- 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: 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] = (BYTE)(cTowerSize-cnt-1);
-
- abTowersNdx[0] = cTowerSize; /* Set disk count for initial post */
- abTowersNdx[1] = abTowersNdx[2] = 0; /* Zero other post counts */
- }
-
-