/*
 * DDE MAZE - SOURCE CODE
 *
 * LANGUAGE 	: Microsoft C
 * MODEL	: Small
 * STATUS	: Operational
 *
 * 09/22/87 - Kevin P. Welch - initial creation.
 *
 * The DDE Maze demonstrates how Dynamic Data Exchange (DDE) can be
 * used in multiple server, multiple client conversations.  The maze
 * supports two distinct data items - one used in passing the animated
 * ball between instances of the Maze, the other for reporting vital
 * Maze statistics to an interested listener.
 *
 * Note that the DDE techniques demonstrated in this program are
 * a simplification of the general specification.  In ALL cases
 * where you observe differences between this application and the
 * specification, please follow the published protocol.
 *
 * Special thanks to Ed Fries, Geoff Nichols, and David West as
 * they helped me see the forest from the trees!
 *
 */
 
#include <windows.h>
#include "maze.h"
#include "maze.d"

ATOM		aWnd;		/* window number item */
ATOM		aBall;		/* bouncing ball topic */
ATOM		aGrab;		/* grab the ball item */
ATOM		aStat;		/* statistics item */
ATOM		aAnyMaze;	/* generic maze atom */
ATOM		aThisMaze;	/* this maze atom */

MAZE		Maze;		/* maze data structure */		
BALL		Ball;		/* ball data structure */
LINK		Link[MAX_LINK];	/* maze communications link */


/*
 * MAZE MAINLINE & MESSAGE PROCESSING LOOP
 *
 *	hInst		current instance handle
 *	hPrevInst	previous instance handle
 *	lpsCmd		execution command line string
 *	wCmdShow		initial show-window option
 *
 * This mainline is responsible for calling the initialization and
 * termination routines in addition to processing and distributing
 * all incoming messages.  Note the revised message processing
 * loop used to animate the bouncing ball.
 *
 */
 
WORD PASCAL WinMain( hInst, hPrevInst, lpsCmd, wCmdShow )
	HANDLE		hInst;
	HANDLE		hPrevInst;
	LPSTR		lpsCmd;
	WORD		wCmdShow;
{
	MSG		Msg;		/* current system message */
	HWND		hWnd;		/* maze window handle */

	/* create & initialize maze */
	hWnd = CreateMaze( hInst, hPrevInst, wCmdShow );

	/* message processing loop */
	do {
	
		/* retrieve next message */
		if ( Ball.bIsBouncing ) {			
			if ( PeekMessage(&Msg,NULL,0,0,TRUE) ) {
				if ( Msg.message != WM_QUIT ) {
					TranslateMessage( &Msg );
					DispatchMessage( &Msg );
				}
			} else
				BounceBall( hWnd );			
		} else 
			if ( GetMessage(&Msg,NULL,0,0) ) {
				TranslateMessage( &Msg );
				DispatchMessage( &Msg );
			}
	
	} while ( Msg.message != WM_QUIT );

	/* destroy maze & exit */
	exit( DestroyMaze(Msg.wParam) );

}


/*
 * MAZE WINDOW MESSAGE PROCESSING FUNCTION
 *
 *	hWnd			window handle
 *	wMsg			window message number
 *	wPrm			additional message info
 *	lPrm			additional message info
 *
 * This function processes all the messages related to the maze
 * window, including all the DDE messages required in order to
 * participate in one or more conversations.  Note that this
 * window function handles DDE messages for both the client and
 * the server sides of the conversation!
 *
 */
 
LONG FAR PASCAL MazeWndFn( hWnd, wMsg, wPrm, lPrm )
	HWND		hWnd;
	WORD		wMsg;
	WORD		wPrm;
	LONG		lPrm;
{
	WORD		i;		/* channel number */
	LONG		lAck;		/* acknowledgement of message */

	/* initialization */
	lAck = FALSE;
	
	/* process message */
	switch( wMsg )
		{
	case WM_CREATE : /* create window */
		
		/* adjust window position if tileable */
		if ( Maze.wNum <= MAZE_ROWS*MAZE_COLS )
			MoveWindow(
				hWnd,
				(Maze.wWidth*((Maze.wNum-1)%MAZE_COLS)),
				(Maze.wHeight*((Maze.wNum-1)/MAZE_COLS)),
				Maze.wWidth,
				Maze.wHeight,
				TRUE
			);
			
		break;
	case WM_SYSCOMMAND : /* system command */
		
		/* process sub-message */
		switch( wPrm )
			{
		case SC_GRAB_BALL :			
			
			/* inform all clients */
			if ( Maze.bGrabBall ) {
				Maze.bGrabBall = FALSE;
				for (i=0; i<Maze.wLinks; i++)
					DDE_UNADVISE( Link[i].hWnd, aGrab );
			} else {
				Maze.bGrabBall = TRUE;
				for (i=0; i<Maze.wLinks; i++)
					DDE_ADVISE( Link[i].hWnd, aGrab );
			}
			
			/* update system menu */
			CheckMenuItem( 
				GetSystemMenu(hWnd,FALSE), 
				SC_GRAB_BALL,
				(Maze.bGrabBall)?MF_CHECKED:MF_UNCHECKED
			);
			
			break;
		case SC_GRAB_FOCUS :
			
			/* adjust state & system menu */
			Maze.bGrabFocus = (Maze.bGrabFocus) ? FALSE : TRUE;
			CheckMenuItem( 
				GetSystemMenu(hWnd,FALSE), 
				SC_GRAB_FOCUS,
				(Maze.bGrabFocus)?MF_CHECKED:MF_UNCHECKED
			);
			
			break;
		default :
			lAck = DefWindowProc( hWnd, wMsg, wPrm, lPrm );
			break;
		}
		
		break;
	case WM_GETMINMAXINFO : /* get window size info */
		
		/* set minimum tracking height */
		((LPPOINT)lPrm)[3].y = 5 * HOLE_HEIGHT;
		
		break;
	case WM_SIZE : /* window being sized */
		
		/* adjust animation statistics */
		Ball.lTimeIn = GetCurrentTime();

		/* calculate hole adjusted window dimensions */
		Maze.wWidth = LOWORD(lPrm) - HOLE_WIDTH;
		Maze.wHeight = HIWORD(lPrm) - HOLE_HEIGHT;
			
		/* randomly position all holes */
		for ( i=0; i<Maze.wLinks; i++ ) {
			Link[i].rHole.left = rand() % Maze.wWidth;
			Link[i].rHole.top = rand() % Maze.wHeight;
			Link[i].rHole.right = Link[i].rHole.left + HOLE_WIDTH;
			Link[i].rHole.bottom = Link[i].rHole.top + HOLE_HEIGHT;
		}
			
		/* calculate ball adjusted window dimensions */
		Maze.wWidth = LO - BALL_WIDTH;
		Maze.wHeight = HI - BALL_HEIGHT;
			
		/* randomly position bouncing ball */
		Ball.rPosn.left = rand() % Maze.wWidth;
		Ball.rPosn.top = rand() % Maze.wHeight;
		Ball.rPosn.right = Ball.rPosn.left + BALL_WIDTH;
		Ball.rPosn.bottom = Ball.rPosn.top + BALL_HEIGHT;
		
		/* assign ball movement direction */
		Ball.iHorzMotion = RANDOM_MOTION;
		Ball.iVertMotion = RANDOM_MOTION;
						
		break;
	case WM_PAINT : /* window needs painting */
		
		{
			PAINTSTRUCT	Region;	/* temporary paint structure */
			BYTE		sInterior[8];  /* temporary text 
                                                          string */
		
			BeginPaint( hWnd, &Region );
			
			/* draw all holes in window */
			for ( i=0; i<Maze.wLinks; i++ ) {
			
				/* select appropriate text & brush colors */
				if ( Link[i].bAdviseBall || Link[i].bAdviseStat ) {
					SetBkColor( Region.hdc, RGB_BLACK );
					SetTextColor( Region.hdc, RGB_WHITE );
					SelectObject( Region.hdc, GetStockObject(BLACK_BRUSH) );
				} else {
					SetBkColor( Region.hdc, RGB_WHITE );
					SetTextColor( Region.hdc, RGB_BLACK );
					SelectObject( Region.hdc, GetStockObject(WHITE_BRUSH) );
				}
				
				/* draw black hole  */
				Rectangle(
					Region.hdc, 
					Link[i].rHole.left, 
					Link[i].rHole.top, 
					Link[i].rHole.right, 
					Link[i].rHole.bottom 
				);
				
				if ( Link[i].wNum )
					sprintf( sInterior, "%u", Link[i].wNum );
				else
					strcpy( sInterior, "?" );
				
				DrawText(
					Region.hdc,
					sInterior,
					strlen(sInterior),
					&Link[i].rHole,
					DT_CENTER|DT_VCENTER|DT_NOCLIP|DT_SINGLELINE
				);
			
			}
				
			EndPaint( hWnd, &Region );
		
		}
		
		break;
	case WM_CLOSE : /* end it all */

		if ( Maze.wLinks ) {
		
			/* pass ball to first interested client */
			if ( Ball.bIsBouncing ) {
				for (i=0; (i<Maze.wLinks)&&(!Link[i].bAdviseBall); i++);
				DDE_DATA(
					Link[ (i<Maze.wLinks) ? i : 0 ].hWnd, 
					aGrab, 
					FALSE,
					(LONG)Maze.wNum,
					(LONG)((GetFocus()==hWnd)?TRUE:FALSE)
				);
			}
	
			/* notify all clients */
			for (i=0; i<Maze.wLinks; i++) {
				if (Link[i].bAdviseBall) DDE_UNADVISE(Link[i].hWnd,aBall);
				if (Link[i].bAdviseStat) DDE_UNADVISE(Link[i].hWnd,aStat);
				DDE_TERMINATE( Link[i].hWnd );
			}

			/* wait till someone pulls the plug */
			Maze.wGoingAway = Maze.wLinks;

			Maze.wLinks = 0;
			Ball.bIsBouncing = FALSE;
			SetWindowText( hWnd, "Maze dying..." );
			
		} else
			DestroyWindow( hWnd );
		
		break;
	case WM_DESTROY : /* end it all! */
		PostQuitMessage( 0 );
		break;
	case WM_DDE_INITIATE : /* initiate DDE conversation */
		
		/* process message if interesting request */
		if ( (hWnd!=wPrm)&&(LO==aAnyMaze||LO==aThisMaze)&&(HI==aBall) ) {
		
			/* check if caller already a client */
			if ( !FindLink(&i,wPrm) ) {

				/* add caller to list of links */
				i = Maze.wLinks++;

				Link[i].wNum = 0;
				Link[i].hWnd = (HWND)wPrm;
				Link[i].bAdviseBall = FALSE;
				Link[i].bAdviseStat = FALSE;

				/* re-add global atoms */
				BumpGlobalAtom( HI );
				BumpGlobalAtom( aThisMaze );
			
				SEND( wPrm, WM_DDE_ACK, MAKELONG(aThisMaze,HI) );

				/* ask caller to become a server also */
				if ( (!Maze.bInitiate)&&(LO==aAnyMaze) ) {
					Maze.bInitiate = TRUE;
					DDE_INITIATE( wPrm, aAnyMaze, aBall );
					Maze.bInitiate = FALSE;
				}

				/* rearrange window & update display */
				SEND(hWnd,WM_SIZE,MAKELONG(DISPLAY_WIDTH,DISPLAY_HEIGHT));				
				InvalidateRect( hWnd, NULL, TRUE );

			}

		}
		
		break;
	case WM_DDE_ADVISE : /* advise client on DDE item */
		
		{
			WORD	wAnswer;   /* answer to advise message */
			LPDATA	lpData;	   /* temporary advise structure */
	
			/* initialization */
			wAnswer = REJECTED;
			
			/* search for link */
			if ( FindLink(&i,wPrm)&&((HI==aGrab)||(HI==aStat)) ) {
				
				lpData = (LPDATA)GlobalLock( LO );
				if ( lpData ) {
							
					if ( lpData->cfFormat == CF_TEXT ) {
					
						wAnswer = ACCEPTED;
						Link[i].bAdviseBall |= (HI==aGrab);
						Link[i].bAdviseStat |= (HI==aStat);
					
						GlobalUnlock( (HANDLE)LO );
						GlobalFree( (HANDLE)LO );
						
						InvalidateRect( hWnd, NULL, TRUE );
						
					} else
						GlobalUnlock( LO );
							
				}
				
			} 
			
			/* respond to message */
			DDE_ACK( wPrm, wAnswer, HI );
		
		}
		
		break;
	case WM_DDE_UNADVISE : /* stop advising client on DDE item */
		
		{
			WORD	wAnswer;  /* answer to advise message */
			LPDATA	lpData;	  /* temporary advise structure */
	
			/* initialization */
			wAnswer = REJECTED;
			
			/* search for link */
			if ( FindLink(&i,wPrm)&&((HI==aGrab)||(HI==aStat)) ) {
				
				wAnswer = ACCEPTED;
				if (HI==aGrab) Link[i].bAdviseBall=FALSE;
				if (HI==aStat) Link[i].bAdviseStat=FALSE;
					
				InvalidateRect( hWnd, NULL, TRUE );
						
			} 
			
			/* respond to message */
			DDE_ACK( wPrm, wAnswer, HI );
		
		}
		
		break;
	case WM_DDE_REQUEST : /* client requestion data item */
		
		{
			WORD	wAnswer;   /* answer to advise message */
			LPDATA	lpData;	   /* temporary advise structure */
	
			if ( FindLink(&i,wPrm)&&(HI==aStat)&&(LO==CF_TEXT) ) 
				DDE_DATA(
					Link[i].hWnd, 
					aStat,
					TRUE, 
					(Ball.bIsBouncing)?GetCurrentTime()-Ball.lTimeIn:0L,
					DISPLAY_WIDTH*(LONG)DISPLAY_HEIGHT
				);
			else
				DDE_ACK( wPrm, REJECTED, HI );
		
		}
			
		break;
	case WM_DDE_POKE : /* client providing unsolicited data item */
	case WM_DDE_DATA : /* server providing data item */
		
		{
			WORD	wData1;		/* window number */
			WORD	wData2;		/* focus state */
			LPDATA	lpData;		/* temporary advise structure */
			BOOL	bRespond;	/* boolean respond flag */
			WORD	wResponse;	/* response to message */
			char	sString[30];	/* temporary data string */
	
			/* initialization */
			bRespond = TRUE;
			wResponse = REJECTED;

			/* search for link & check data item */
			if ( FindLink(&i,wPrm)&&((HI==aGrab)||(HI==aWnd)) ) {
						
				/* retrieve data - lock may not succeed */
				lpData = (LPDATA)GlobalLock( LO );
				if ( lpData ) {
									
					/* check format */
					if ( lpData->cfFormat == CF_TEXT ) {
						
						/* extract data */
						lstrcpy( (LPSTR)sString, lpData->info );				
						sscanf( sString, "%u\t%u", &wData1, &wData2 );
						
						/* process data */
						if ( HI == aGrab ) {

							/* grab ball */
							Ball.bIsBouncing = TRUE;
							Ball.lTimeIn = GetCurrentTime();
							if (Maze.bGrabFocus && wData2) SetFocus(hWnd);
							
							/* randomize display */
							SEND(
								hWnd,
								WM_SIZE,
								MAKELONG(DISPLAY_WIDTH,DISPLAY_HEIGHT)
							);				

						} else 
							Link[i].wNum = wData1;				
			
						/* Determine if acknowledgement required */
						if ( !lpData->fAck ) {
							bRespond = FALSE;
							GlobalDeleteAtom( HI );
						} else
							wResponse = ACCEPTED;
									
						/* unlock memory & free if required */
						if ( lpData->fRelease ) {
							GlobalUnlock( LO );
							GlobalFree( LO );
						} else
							GlobalUnlock( LO );
						
						/* force system to repaint display */
						InvalidateRect( hWnd, NULL, TRUE );		
						
					} else
						GlobalUnlock( LO );
											
				} 
												
			} 
					
			/* respond to caller */
			if (bRespond) DDE_ACK(wPrm,wResponse,HI);
		
		}

		break;
	case WM_DDE_ACK : /* DDE acknowledgement */

		if ( Maze.bInitiate ) {
			
			/* delete atoms - since bumped */
			GlobalDeleteAtom( LO );
			GlobalDeleteAtom( HI );
			
			/* inform server of window number */
			DDE_POKE(
				wPrm, 
				aWnd,
				FALSE, 
				(LONG)Maze.wNum,
				(LONG)((GetFocus()==hWnd)?TRUE:FALSE)
			);

			/* inform server of current advise status */
			if ( Maze.bGrabBall ) 
				DDE_ADVISE( wPrm, aGrab );
			
		}

		break;
	case WM_DDE_TERMINATE : /* end a DDE conversation */

		if ( FindLink(&i,wPrm) ) {
		
			/* respond with an unadvise on all items */
			if (Link[i].bAdviseBall) DDE_UNADVISE(wPrm,aBall);
			if (Link[i].bAdviseStat) DDE_UNADVISE(wPrm,aStat);
			
			/* remove caller from list */
			memcpy(&Link[i],&Link[i+1],(Maze.wLinks-i-1)*sizeof(LINK));
			Maze.wLinks--;
			
			/* respond with a matching terminate & update display */
			DDE_TERMINATE( wPrm );
			InvalidateRect( hWnd, NULL, TRUE );
		
		} else
			if ( (Maze.wGoingAway)&&(Maze.wGoingAway-- == 1) )
				DestroyWindow( hWnd );
		
		break;
	default : /* pass on to default */
		lAck = DefWindowProc( hWnd, wMsg, wPrm, lPrm );
		break;
	}
	
	/* return result */
	return( lAck );

}


/*
 * CREATE DDE MAZE
 *
 *	hInst		current instance handle
 *	hPrevInst	previous instance handle
 *	wCmdShow	initial show window command
 *
 * This function creates and initializes the Maze, including the
 * definition of all global atoms.  A handle to the maze window is
 * returned if the entire process is successful.
 *
 */
 
static HWND CreateMaze( hInst, hPrevInst, wCmdShow )
	HANDLE	hInst;
	HANDLE	hPrevInst;
	WORD	wCmdShow;
{
	WORD		i;		/* temporary loop variable */
	HWND		hWnd;		/* new maze window handle */
	HMENU		hMenu;		/* system menu handle */
	WORD		wQueue;		/* queue length counter */
	BOOL		bSearch;	/* boolean search flag */
	BOOL		bPresent;	/* number present flag */
	WNDCLASS	WndClass;	/* window class structure */
	BYTE		sCaption[64];	/* current window caption */
	
	/* perform instance specific initialization */
	if ( !hPrevInst ) {
	
		Maze.wNum = 1;

		Ball.bIsBouncing = TRUE;
		Ball.lTimeIn = GetCurrentTime();

		memset( &WndClass, 0, sizeof(WNDCLASS) );
		
		WndClass.lpszClassName = (LPSTR)"MazeWindow";
		WndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
		WndClass.lpszMenuName = (LPSTR)NULL;
		WndClass.style = CS_HREDRAW | CS_VREDRAW;
		WndClass.lpfnWndProc = 	MazeWndFn;
		WndClass.hInstance = hInst;
		WndClass.hIcon = NULL;
		WndClass.hbrBackground = (HBRUSH)(COLOR_MENU + 1);

	} else {

		GetInstanceData( hPrevInst, (NPSTR)&Maze, sizeof(MAZE) );
		GetInstanceData( hPrevInst, (NPSTR)Link, MAX_LINK*sizeof(LINK) );
				
		Link[Maze.wLinks++].wNum = Maze.wNum;

		bSearch = TRUE;
		Maze.wNum = 1;

		while ( bSearch ) {
		
			bPresent = FALSE;
			for (i=0; i<Maze.wLinks; i++)
				if ( Link[i].wNum == Maze.wNum )
					bPresent = TRUE;
					
			if ( bPresent )
				Maze.wNum++;
			else
				bSearch = FALSE;
		
		}		
		
		Maze.wLinks = 0;
		Ball.bIsBouncing = FALSE;
	
	}

	/* continue Maze initialization */
	if ( hPrevInst || RegisterClass(&WndClass) ) {
	
		/* adjust maze queue length */
		wQueue = 42;
		while ( !SetMessageQueue(wQueue--) );
		
		/* define caption & initial position */
		sprintf( sCaption, "DDE Maze - #%u", Maze.wNum );
			
		Maze.wGoingAway = 0;
		Maze.bGrabBall = FALSE;
		Maze.bGrabFocus = FALSE;
		Maze.wWidth = GetSystemMetrics(SM_CXSCREEN) / MAZE_COLS;	
		Maze.wHeight = GetSystemMetrics(SM_CYSCREEN) / MAZE_ROWS;
				
		hWnd = CreateWindow(
				"MazeWindow",		/* class name */
				sCaption,		/* caption */
				WS_OVERLAPPEDWINDOW,	/* style */
				0,			/* x position */
				0,			/* y position */
				0,			/* width */
				0,			/* height */
				(HWND)NULL,		/* parent window */
				(HMENU)NULL,		/* menu */
				hInst,			/* application */
				(LPSTR)NULL		/* other data */
			);

		if ( hWnd ) {
		
			/* revise system menu */		
			hMenu = GetSystemMenu( hWnd, FALSE );
			ChangeMenu(hMenu,0,NULL,0,MF_APPEND|MF_SEPARATOR );
			ChangeMenu(hMenu,0,"Grab the ball",SC_GRAB_BALL,MF_APPEND);
			ChangeMenu(hMenu,0,"Grab the focus",SC_GRAB_FOCUS,MF_APPEND);
		
			/* define global atoms */
			sprintf( sCaption, "Maze%u", Maze.wNum );

			aAnyMaze = GlobalAddAtom( "Maze" );
			aThisMaze = GlobalAddAtom( sCaption );
			aBall = GlobalAddAtom( "Ball" );
			aWnd = GlobalAddAtom( "Window" );
			aGrab = GlobalAddAtom( "Grab" );
			aStat = GlobalAddAtom( "Statistics" );
				
			/* initiate DDE conversation on ball */
			Maze.bInitiate = TRUE;
			DDE_INITIATE( -1, aAnyMaze, aBall );
			Maze.bInitiate = FALSE;

			/* seed random number & display maze */
			srand( hInst );
			ShowWindow( hWnd, wCmdShow );
		
		} else
			exit( 2 );
	
	} else
		exit( 1 );
		
	/* return final result */
	return( hWnd );
	
}


/*
 * BOUNCE BALL IN DDE MAZE
 *
 *	hWnd	maze window handle
 *
 * This function animates the DDE Maze by moving the ball around the
 * window.  If the ball "falls" into a "hole" a DDE message is
 * posted to the appropriate client and the ball transferred.  In
 * addition, various summary statistics are also provided for those
 * interested listeners.  
 *
 */
 
static VOID BounceBall( hWnd )
	HWND	hWnd;
{
	WORD	i;		/* client channel number */
	HDC	hDC;		/* temporary display context */
	LONG	lElapsed;	/* elapsed time ball was bouncing */

	/* check if ball inside hole */
	for ( i=0; (i<Maze.wLinks)&&(OUTSIDE_HOLE); i++ );

	/* animate ball if not in hole */
	if ( i == Maze.wLinks ) {

		hDC = GetDC( hWnd );
		
		/* Note - you could erase the bouncing ball here using an
		 * InvertRect call with the current ball position.  Leaving it
		 * out results in an interesting visual effect!
		 */
		
		Ball.iHorzMotion = H_BOUNCE( Ball.rPosn.left, Maze.wWidth );
		Ball.iVertMotion = V_BOUNCE( Ball.rPosn.top, Maze.wHeight );
					
		/* compute new ball position & draw */
		Ball.rPosn.top += Ball.iVertMotion;
		Ball.rPosn.left += Ball.iHorzMotion;
		Ball.rPosn.right += Ball.iHorzMotion;
		Ball.rPosn.bottom += Ball.iVertMotion;	
		
		InvertRect( hDC, &Ball.rPosn );		
		ReleaseDC( hWnd, hDC );

	} else {
		
		/* pass ball to client */
		Ball.bIsBouncing = FALSE;
		DDE_DATA(
			Link[i].hWnd, 
			aGrab,
			FALSE, 
			(LONG)Maze.wNum,
			(LONG)((GetFocus()==hWnd)?TRUE:FALSE)
		);

		/* calculate animation statistics */
		lElapsed = GetCurrentTime() - Ball.lTimeIn;
			
		/* inform all statistics clients */
		for ( i=0; i<Maze.wLinks; i++ )
			if ( Link[i].bAdviseStat ) 
				DDE_DATA(
					Link[i].hWnd, 
					aStat,
					FALSE,
					lElapsed,
					DISPLAY_WIDTH*(LONG)DISPLAY_HEIGHT
				);
	
		InvalidateRect( hWnd, NULL, TRUE );	

	}

}


/*
 * ADVISE SERVER TO SEND DATA
 *
 *	hToWnd		client window handle
 *	hFromWnd	server window handle
 *	aItem		atom representing item
 *
 * This function enables the calling routine to advise a client window
 * regarding a particular item of data.  This function assumes that
 * the data is to be sent in CF_TEXT format without requiring any
 * acknowledgement.  A value of TRUE is returned if the function is
 * successful.
 *
 */
 
static BOOL Advise( hToWnd, hFromWnd, aItem )
	HWND	hToWnd;
	HWND	hFromWnd;
	ATOM	aItem;
{
	/* local variables */
	HANDLE	hMem;		/* temporary memory handle */
	LPDATA	lpData;		/* pointer to data structure */
	BOOL	bResult;	/* result of function */
	
	bResult = FALSE;
	
	/* allocate memory & check if succeeded */
	hMem = GlobalAlloc( GHND|GMEM_DDESHARE, (DWORD)sizeof(DATA) );
	if ( hMem ) {

		/* lock the data - may not suceed with expanded memory */
		lpData = (LPDATA)GlobalLock( hMem );
		if ( lpData ) {

			/* define data structure constants */
			lpData->fAck = FALSE;
			lpData->fNoData = FALSE;
			lpData->cfFormat = CF_TEXT;
			
			/* unlock prior to sending */
			GlobalUnlock( hMem );
			
			/* notify server to send data */
			bResult = PostMessage(
				hToWnd,
				WM_DDE_ADVISE,
				hFromWnd,
				MAKELONG(hMem,aItem)
			);

		} else
			GlobalFree( hMem );

	}
	
	/* return result */
	return( bResult );

} 


/*
 * TRANSMIT DDE DATA TO CLIENT
 *
 *	hToWnd		destination window handle
 *	hFromWnd	server window handle
 *	wMsg		message number to use
 *	aItem		atom representing data item
 *	bResp		data in response to a request
 *	l1		first portion data item to send
 *	l2		second portion data item to send
 *
 * This function enables the calling routine to transmit data to a
 * client window using either a DDE_DATA or DDE_POKE messgae.  It is
 * assumed that the information is sent in CF_TEXT format and does not
 * require the client to respond.  A value of TRUE is returned if the
 * entire process is successful.
 *
 */
 
static BOOL Transmit( hToWnd, hFromWnd, wMsg, aItem, bResp, l1, l2 )
	HWND	hToWnd;
	HWND	hFromWnd;
	WORD	wMsg;
	ATOM	aItem;
	BOOL	bResp;
	LONG	l1;
	LONG	l2;
{
	/* local variables */
	HANDLE	hMem;		/* temporary memory handle */
	LPDATA	lpData;		/* pointer to data structure */
	BOOL	bResult;	/* boolean result value */
	char	sString[32];	/* local string variable */
	
	bResult = FALSE;

	/* allocate memory & check if succeeded */
	hMem = GlobalAlloc( GHND|GMEM_DDESHARE, (DWORD)sizeof(DATA) );
	if ( hMem ) {

		/* lock the data - may not suceed with expanded memory */
		lpData = (LPDATA)GlobalLock( hMem );
		if ( lpData ) {

			/* define data structure constants */
			lpData->fAck = FALSE;
			lpData->fRelease = TRUE;
			lpData->fResponse = bResp;
			lpData->cfFormat = CF_TEXT;
			
			BumpGlobalAtom( aItem );

			/* format the data */
			sprintf( sString, "%ld\t%ld", l1, l2 );
			lstrcpy( lpData->info, (LPSTR)sString );
			
			/* unlock prior to sending */
			GlobalUnlock( hMem );
			
			bResult = PostMessage(
					hToWnd,
					wMsg,
					hFromWnd,
					MAKELONG(hMem,aItem)
				);
			
		} else
			GlobalFree( hMem );

	}
	
	/* return result */
	return( bResult );

} 



/*
 * DESTROY DDE MAZE
 *
 *	wQuit	application exit code
 *
 * This function destroys all resources consumed by the Maze during
 * execution.  Included is the removal of all global atoms.  A final
 * exit code is returned by the function.
 *
 */
 
static WORD DestroyMaze( wQuit )
	WORD	wQuit;
{

	/* remove global atoms */
	GlobalDeleteAtom( aAnyMaze );
	GlobalDeleteAtom( aThisMaze );
	GlobalDeleteAtom( aBall );
	GlobalDeleteAtom( aWnd );
	GlobalDeleteAtom( aGrab );
	GlobalDeleteAtom( aStat );

	/* return final exit code */
	return( wQuit );

}



/*
 * FIND DDE LINK
 *
 *	pwNdx	index to link
 *	hWnd	window handle of caller
 *
 * This function finds the link references by the window handle
 * provided.  The resulting link is returned to the caller.  The
 * function returns TRUE if the window handle was found.
 *
 */
 
static BOOL FindLink( pwNdx, hWnd )
	WORD *	pwNdx;
	HWND	hWnd;
{
	WORD	i;
	
	for (i=0; i<Maze.wLinks; i++)
		if ( Link[i].hWnd == hWnd ) {
			*pwNdx = i;
			return( TRUE );
		}
			
	return( FALSE );
		
}


/*
 * BUMP GLOBAL ATOM
 *
 *	aAtom	atom to bump
 *
 * This function increments the reference count to the specified
 * global atom.  It is assumed that the atom supplied is defined and
 * valid.  The value supplied is returned.
 *
 */
 
static ATOM BumpGlobalAtom( aAtom )
	ATOM		aAtom;
{
	char		sName[64];				
	
	GlobalGetAtomName( aAtom, sName, sizeof(sName) );
	return( GlobalAddAtom(sName) );
}	
