/****************************************************************************
*
*  FILE:          LIFE.C
*
*  DESCRIPTION:   Windows 3.0 Version of the classic simulation of "LIFE".
*                 This program incorporates a minor twist in the basic
*                 algorithm by utilizing color to represent various
*                 life "phases".  The final phase of any life cycle is
*                 always death!
*
*                 This software is hereby placed in the public domain.
*                 Use and abuse it at your own risk!
*
*
*  AUTHOR:        Tom Wheeler
*                 31294 Morlock
*                 Livonia, Mich.  48152
*                 [72037,1742]
*
*****************************************************************************
*
*     DATE        VER                     DESCRIPTION
*  ----------     ---     ---------------------------------------------------
*   05/20/91      1.0     Initial Development 1.0
*   05/22/91      1.1     Added Color Support
*
****************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "life.h"

#define CLASSNAME       "LifeClass"
#define APPNAME         "Life"

#define DEAD              0         /* state: cell is dead                 */
#define ALIVE           0x1         /* state: cell is alive                */
#define CHILD           0x2         /* state: cell is a child              */
#define ADOLESCENT      0x4         /* state: cell is an adolescent        */
#define ADULT           0x8         /* state: cell is an adult             */
#define MIDAGED        0x10         /* state: cell is middle aged          */
#define RETIRED        0x20         /* state: cell is retirement age       */
#define SENIOR         0x40         /* state: cell is a senior citizen     */
#define METHUSULA      0x80         /* state: cell is to be terminated     */
#define AGECYCLE         10         /* # of cycles till next age           */

#define MAX_XCELL       100         /* max number of horizontal cells      */
#define MAX_YCELL       100         /* max number of vertical cells        */
#define DEFAULT_WIDTH    15         /* default number of cells to display  */
#define DEFAULT_HEIGHT   15         /* default number of cells to display  */
#define CYCLE_TIMER       1

/* Macro to get a random integer within a specified range */
#define getrandom( min, max ) ((rand() % (int)(((max)+1) - (min))) + (min))

typedef unsigned char UCHAR;

/* MATRIX defines the cell matrix used to represent the grid of entities.
 * Life states are represented as the low byte of the matrix entry, the high
 * byte is used to is used to keep a count representing the age of the
 * individual entities.
 */
typedef int MATRIX[MAX_XCELL+2][MAX_YCELL+2];

typedef struct _Life {
   int CellX;                       /* individual cell width               */
   int CellY;                       /* individual cell height              */
   int TopCellX;                    /* horiz. id of first cell displayed   */
   int TopCellY;                    /* vert. id of frist cell displayed    */
   int CellsPerPageX;               /* num of visible X cells on screen    */
   int CellsPerPageY;               /* num of visible Y cells on screen    */
   BOOL Grid;                       /* grid on/off state                   */
   BOOL Run;                        /* run state of matrix                 */
   MATRIX Matrix;                   /* state matrix                        */
}LIFE;
typedef LIFE *PLIFE;

MATRIX Scratch;                     /* temporary cell matrix               */
HWND hInst = NULL;                	/* instance variable                   */
HMENU hMenuFrame = NULL;           	/* instance menu handle                */
HWND hWndFrame = NULL;              /* instance window handle              */
HANDLE hAccelFrame = NULL;          /* instance keyboard accelerator       */
HANDLE hMatrix = NULL;              /* handle to cell matrix structure     */

HPEN hBluePen = NULL;               /* Blue drawing pen                    */
HPEN hGreenPen = NULL;              /* Green drawing pen                   */
HPEN hCyanPen = NULL;               /* Cyan drawing pen                    */
HPEN hRedPen = NULL;                /* Red drawing pen                     */
HPEN hMagentaPen = NULL;            /* Magenta drawing pen                 */
HPEN hBrownPen = NULL;              /* Brown drawing pen                   */
HPEN hGrayPen = NULL;               /* Gray drawing pen                    */
HPEN hLightRedPen = NULL;           /* Light Red drawing pen               */
HPEN hWhitePen = NULL;              /* White drawing pen                   */

HBRUSH hBlueBrush = NULL;           /* Solid Blue brush                    */
HBRUSH hGreenBrush = NULL;          /* Solid Green brush                   */
HBRUSH hCyanBrush = NULL;           /* Solid Cyan brush                    */
HBRUSH hRedBrush = NULL;            /* Solid Red brush                     */
HBRUSH hMagentaBrush = NULL;        /* Solid Magenta brush                 */
HBRUSH hBrownBrush = NULL;          /* Solid Brown brush                   */
HBRUSH hGrayBrush = NULL;           /* Solid Gray brush                    */
HBRUSH hLightRedBrush = NULL;       /* Solid Light Red brush               */
HBRUSH hWhiteBrush = NULL;          /* Solid White brush                   */

PLIFE pLife = NULL;                 /* pointer to cell life structure      */
int iSmall;                      	/* size in pixels of a small cell      */
int iNorm;                       	/* size in pixels of a normal cell     */
int iLarge;                         /* size in pixels of a large cell      */
int iCycleTime = 250;               /* default timer (milliseconds)        */
WORD wCycleTimer = 0;               /* timer used for life cycling         */

/****************************************************************************
*  void DrawGridLines(HDC hDC,RECT *rect)
*
*  Description:   DrawsGridLines on the Life Display Window
*
*  Input:         hDC   - Device Context to draw on
*                 rect  - pointer to client rectangle
*
*  Output:        N/A
****************************************************************************/
void DrawGridLines(HDC hDC,RECT *rect)
{
   int iX,iY;

   SelectObject(hDC,hGrayPen);
   for(iX = pLife->CellX; iX < rect->right; iX += pLife->CellX) {
      MoveTo(hDC,iX,0);
      LineTo(hDC,iX,rect->bottom);
   }
   for(iY = pLife->CellY; iY < rect->bottom; iY += pLife->CellY) {
      MoveTo(hDC,0,iY);
      LineTo(hDC,rect->right,iY);
   }
}

/****************************************************************************
*  void DrawCell(HDC hMouseMoveDC,int iXPos,int iYPos,UCHAR ucState)
*
*  Description:   Draws the cell at the indicated grid position in the color
*                 appropriate to its current state.
*
*  Input:         hDC   	- Device Context to draw on
*                 iXPos
*                 iYPos    - Screen Matrix position to draw at
*                 ucState  - State to draw
*
*  Output:        N/A
****************************************************************************/
void DrawCell(HDC hDC,int iXPos,int iYPos,UCHAR ucState)
{
   int iX,iY;
   HPEN hPen;
   HBRUSH hBrush;

   iX = iXPos * pLife->CellX;
   iY = iYPos * pLife->CellY;
   if(ucState & ALIVE) {
      if(ucState & SENIOR) {
         hPen = hBluePen;
         hBrush = hBlueBrush;
      }
      else if(ucState & RETIRED) {
         hPen = hBrownPen;
         hBrush = hBrownBrush;
      }
      else if(ucState & MIDAGED) {
         hPen = hMagentaPen;
         hBrush = hMagentaBrush;
      }
      else if(ucState & ADULT) {
         hPen = hRedPen;
         hBrush = hRedBrush;
      }
      else if(ucState & ADOLESCENT) {
         hPen = hCyanPen;
         hBrush = hCyanBrush;
      }
      else if(ucState & CHILD) {
         hPen = hGreenPen;
         hBrush = hGreenBrush;
      }
   }
   else {
      hPen = hWhitePen;
      hBrush = hWhiteBrush;
   }
   SelectObject(hDC,hPen);
   SelectObject(hDC,hBrush);
   Rectangle(hDC,iX+2,iY+2,iX+pLife->CellX-1,iY+pLife->CellY-1);
}

/****************************************************************************
*  void DrawMatrix(HDC hDC)
*
*  Description:   Draws The Matrix (only draws the cells that will be visible
*                 in the display window)
*
*  Input:         hDC - Device Context to draw on
*
*  Output:        N/A
****************************************************************************/
void DrawMatrix(HDC hDC)
{
   int iX,iY,iXPos,iYPos,iCellTemp;

   for(iXPos = 0,iX = pLife->TopCellX;
       iX < pLife->CellsPerPageX + pLife->TopCellX; iX++,iXPos++) {
      for(iYPos = 0,iY = pLife->TopCellY;
          iY < pLife->CellsPerPageY + pLife->TopCellY; iY++,iYPos++) {
         iCellTemp = pLife->Matrix[iX][iY] & 0xff;
         if(iCellTemp & ALIVE)
            DrawCell(hDC,iXPos,iYPos,(UCHAR)iCellTemp);
      }
   }
}

/****************************************************************************
*  void CycleMatrix(HDC hDC)
*
*  Description:   Runs the Life Algorithm on the Cell Matrix (only updates
*                 the cells that would be visible, the matrix is forced to
*                 wrap at the limits of the screen matrix).
*
*  Input:         hDC - Device Context to draw on
*
*  Output:        N/A
****************************************************************************/
void CycleMatrix(HDC hDC)
{
   register int iX,iY;
   int iXPos,iYPos,iNeighbor,iCellTemp;
   UCHAR ucState;
   BOOL bNoneAlive = TRUE;

   for(iX = pLife->TopCellX,iXPos = 0; iX < (pLife->CellsPerPageX +
       pLife->TopCellX); iX++,iXPos++) {
      for(iY = pLife->TopCellY,iYPos = 0; iY < (pLife->CellsPerPageY +
          pLife->TopCellY); iY++,iYPos++) {
         iNeighbor = 0;
         if(pLife->Matrix[iX-1][iY] & ALIVE)
            iNeighbor++;
         if(pLife->Matrix[iX+1][iY] & ALIVE)
            iNeighbor++;
         if(pLife->Matrix[iX][iY-1] & ALIVE)
            iNeighbor++;
         if(pLife->Matrix[iX][iY+1] & ALIVE)
            iNeighbor++;
         if(pLife->Matrix[iX-1][iY-1] & ALIVE)
            iNeighbor++;
         if(pLife->Matrix[iX+1][iY+1] & ALIVE)
            iNeighbor++;
         if(pLife->Matrix[iX-1][iY+1] & ALIVE)
            iNeighbor++;
         if(pLife->Matrix[iX+1][iY-1] & ALIVE)
            iNeighbor++;
         ucState = (UCHAR)pLife->Matrix[iX][iY];
         if(ucState == DEAD) {   /* birth of a cell if 3 neighbors present */
            if(iNeighbor == 3) {
               Scratch[iX][iY] = ALIVE | CHILD;
               DrawCell(hDC,iXPos,iYPos,ALIVE | CHILD);
            }
            else {
               Scratch[iX][iY] = pLife->Matrix[iX][iY];
            }
         }
         else {
            bNoneAlive = FALSE;
            if((iNeighbor < 2) || (iNeighbor > 3)) { /* kill it off */
               Scratch[iX][iY] = DEAD;
               DrawCell(hDC,iXPos,iYPos,DEAD);
            }
            else {
               /* Keep cell and increment cycle count.  If cycle count limit
                * is reached, advance cell to next life cycle
                */
               Scratch[iX][iY] = pLife->Matrix[iX][iY] + 0x100;
               if(!((Scratch[iX][iY] & 0xff00) % (AGECYCLE << 8))) {
                  iCellTemp = ((Scratch[iX][iY] & 0xff) << 1) | ALIVE;
                  if(iCellTemp <= METHUSULA)
                     Scratch[iX][iY] = (Scratch[iX][iY] & 0xff00) + iCellTemp;
                  else {
                     /* if it has aged past SENIOR, kill it off */
                     Scratch[iX][iY] = iCellTemp = DEAD;
                  }
                  DrawCell(hDC,iXPos,iYPos,(UCHAR)iCellTemp);
               }
            }
         }
      }
   }
   /* copy all changes to permanent cell matrix */
   memmove(&pLife->Matrix,&Scratch,sizeof(MATRIX));
   /* randomize the matrix if no alive cells are displayed */
   if(bNoneAlive) {
      for(iX = pLife->TopCellX; iX < (pLife->CellsPerPageX +
          pLife->TopCellX); iX++) {
         for(iY = pLife->TopCellY; iY < (pLife->CellsPerPageY +
             pLife->TopCellY); iY++) {
            if(getrandom(0,1)) {
               pLife->Matrix[iX][iY] = ALIVE | CHILD;
            }
         }
      }
   }
}

/****************************************************************************
*  void UpdateScrollBarRange(HWND hWnd)
*
*  Description:   Updates the Scroll Bar Ranges
*
*  Input:         hWnd - Window Handle owning the scroll bars
*
*  Output:        N/A
****************************************************************************/
void UpdateScrollBarRange(HWND hWnd)
{
   RECT rect;

   GetClientRect(hWnd,&rect);
   pLife->CellsPerPageX = (rect.right / pLife->CellX) + 1;
   pLife->CellsPerPageY = (rect.bottom / pLife->CellY) + 1;
   pLife->TopCellX = max(1,min(pLife->TopCellX,MAX_XCELL -
                     pLife->CellsPerPageX));
   pLife->TopCellY = max(1,min(pLife->TopCellY,MAX_YCELL -
                     pLife->CellsPerPageY));
   SetScrollRange(hWnd,SB_HORZ,1,MAX_XCELL - pLife->CellsPerPageX,TRUE);
   SetScrollRange(hWnd,SB_VERT,1,MAX_YCELL - pLife->CellsPerPageY,TRUE);
}

/****************************************************************************
*  BOOL InitLife(void)
*
*  Description:   Life Initialization
*
*  Input:         N/A
*
*  Output:        Fails if no memory available
****************************************************************************/
BOOL InitLife(void)
{
   int iWidth,iHeight,iX,iY;

   /* allocate local storage to hold the Life Cell Matrix */
   if((hMatrix = LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT,
                             sizeof(LIFE))) == NULL)
      return FALSE;
   pLife = (PLIFE)LocalLock(hMatrix);  /* lock down the memory permanently */

   /* set the default Life cell values */
   iX = GetSystemMetrics(SM_CXSCREEN);
   iY = GetSystemMetrics(SM_CYSCREEN);
   iNorm = iY / 25;
   iSmall = iNorm / 2;
   iLarge = iNorm * 2;
   pLife->CellX = iNorm;
   pLife->CellY = iNorm;
   pLife->Grid = TRUE;
   pLife->Run = FALSE;
   pLife->TopCellX = pLife->TopCellY = 1;
   CheckMenuItem(hMenuFrame,MENU_NORM,MF_BYCOMMAND | MF_CHECKED);
   /* initialize all cells to DEAD */
   for(iWidth = 0; iWidth < MAX_XCELL; iWidth++)
      for(iHeight = 0; iHeight < MAX_YCELL; iHeight++)
         pLife->Matrix[iWidth][iHeight] = DEAD;
   iWidth = (pLife->CellX * DEFAULT_WIDTH) +
            (GetSystemMetrics(SM_CXFRAME) * 2) +
            GetSystemMetrics(SM_CXHTHUMB);
   iHeight = (pLife->CellY * DEFAULT_HEIGHT) +
             (GetSystemMetrics(SM_CYFRAME) * 2) +
             GetSystemMetrics(SM_CYMENU) +
             GetSystemMetrics(SM_CYVTHUMB) +
             GetSystemMetrics(SM_CYCAPTION);
   SetWindowPos(hWndFrame,NULL,(iX - iWidth) / 2,(iY - iHeight) / 2,
                iWidth,iHeight,SWP_NOZORDER);
}

/****************************************************************************
*  CenterDialog(HWND hDlg)
*
*  Description:   Function to Center a Dialog Box on the Screen
*
*  Input:         hDlg - Handle to Dialog Box to be centered
*
*  Output:        N/A
*****************************************************************************/
void CenterDialog(HWND hDlg)
{
   RECT rect;
   int iX,iY,iNewX,iNewY;

   GetWindowRect(hDlg,(RECT far *)&rect);
   iX = GetSystemMetrics(SM_CXSCREEN);
   iY = GetSystemMetrics(SM_CYSCREEN);
   iNewX = (iX/2) - ((rect.right-rect.left)/2);
   iNewY = (iY/2) - ((rect.bottom-rect.top)/2);
   SetWindowPos(hDlg,NULL,max(iNewX,0),max(iNewY,0),rect.right-rect.left,
                rect.bottom-rect.top,SWP_NOZORDER);
}

/****************************************************************************
*
*  BOOL FAR PASCAL TimerEditDlgProc(HWND hDlg,int iMessage,WORD wParam,
*                                  LONG lParam)
*
*  DESCRIPTION:   Life Cycle Time Edit Dialog Box Proc
*
*  INPUT:         hDlg - Handle to Dialog Box
*                 iMessage - Dialog Box message
*                 wParam
*                 lParam - Standard Windows message parameters
*                 Called externally from WINDOWS - Exported Function
*
*  OUTPUT:        N/A
*
****************************************************************************/
BOOL FAR PASCAL TimerEditDlgProc(HWND hDlg,int iMessage,WORD wParam,
                                LONG lParam)
{
   BOOL bTransFlag;
   WORD wVal;
   char szTime[10];

   switch (iMessage) {
      case WM_COMMAND:
         switch(wParam) {
            case IDOK:
               wVal = GetDlgItemInt(hDlg,TIMER_EDIT,(BOOL FAR *)&bTransFlag,
                                    FALSE);
               if((!bTransFlag) || (wVal < 100) || (wVal > 999)) {
                  MessageBeep(0);
                  sprintf(szTime,"%d",iCycleTime);
                  SetDlgItemText(hDlg,TIMER_EDIT,(LPSTR)szTime);
               }
               else {
                  iCycleTime = wVal;
                  EndDialog(hDlg,TRUE);
               }
               break;

            case IDCANCEL:
               EndDialog(hDlg,FALSE);
               break;
         }
         break;

      case WM_INITDIALOG:
         sprintf(szTime,"%d",iCycleTime);
         SetDlgItemText(hDlg,TIMER_EDIT,(LPSTR)szTime);
         CenterDialog(hDlg);
         return(TRUE);

      default:
         return(FALSE);
   }
   return(FALSE);
}

/****************************************************************************
*  AboutDlgProc(hDlg,iMessage,wParam,lParam)
*
*  Description:   About Dialog Box Procedure
*
*  Input:         hDlg  	- Handle to Dialog Box
*                 uMessage - Dialog Box message
*                 wParam
*                 lParam 	- Standard Windows message parameters
*                 Called externally from WINDOWS
*
*  Output:        N/A
*****************************************************************************/
BOOL FAR PASCAL AboutDlgProc(HWND hDlg,unsigned iMessage,
                             WORD wParam,LONG lParam)
{
   BOOL Ret;
   char szTimeStamp[128];

   Ret = FALSE;
   switch (iMessage)
   {
      case WM_INITDIALOG:
         sprintf(szTimeStamp,"Built: %s %s",__DATE__,__TIME__);
         SetDlgItemText(hDlg,ABOUT_TIMESTAMP,szTimeStamp);
         CenterDialog(hDlg);
         Ret = TRUE;
         break;

      case WM_COMMAND:
         EndDialog(hDlg, TRUE);
         Ret = TRUE;
         break;
   }
   return Ret;
}

/****************************************************************************
*  WndProc(hWnd,iMessage,wParam,lParam)
*
*  Description:   Main Window Proc
*
*  Input:         hWnd  	- Handle to window
*                 iMessage - Window message number
*                 wParam,
*                 lParam 	- Standard Windows message parameters
*                 Called externally from WINDOWS
*
*  Output:        Calls DefWindowProc() while active,returns 0L ((LONG) FALSE)
*                 when terminating.
****************************************************************************/
long FAR PASCAL WndProc(HWND hWnd,unsigned iMessage,WORD wParam,LONG lParam)
{
   FARPROC lpProc;
   long    Ret;
   PAINTSTRUCT ps;
   static RECT rect;
   HDC hDC;
   WORD wXMouse,wYMouse;
   UCHAR ucState;
   static int iXDown,iYDown;
   static BOOL bMouseDown = FALSE,bIconic = FALSE;
   static HDC hMouseMoveDC = NULL;
   int iXCell,iYCell;

   Ret = (long)FALSE;

   switch(iMessage)
   {
      case WM_COMMAND:
         switch (wParam)
         {
            case MENU_RUN:
               if(pLife->Run) {
                  /* stop the Cycle Timer */
                  if(wCycleTimer) {
                     KillTimer(hWnd,CYCLE_TIMER);
                     wCycleTimer = 0;
               	}
                  ModifyMenu(hMenuFrame,MENU_RUN,MF_BYCOMMAND,MENU_RUN,
                             "St&art\t^R");
                  EnableMenuItem(hMenuFrame,MENU_STEP,MF_ENABLED);
                  pLife->Run = FALSE;
               }
               else {
                  /*start the cycle timer */
                  if((wCycleTimer =
                     SetTimer(hWnd,CYCLE_TIMER,iCycleTime,NULL)) == 0) {
                     MessageBox(hWnd,"No More System Timers!",
                        NULL,MB_OK | MB_ICONEXCLAMATION);
                     break;
                  }
                  ModifyMenu(hMenuFrame,MENU_RUN,MF_BYCOMMAND,MENU_RUN,
                             "St&op\t^R");
                  pLife->Run = TRUE;
                  EnableMenuItem(hMenuFrame,MENU_STEP,MF_GRAYED);
               }
               break;

            case MENU_STEP:
               hDC = GetDC(hWnd);
               CycleMatrix(hDC);
               ReleaseDC(hWnd,hDC);
               InvalidateRect(hWnd,NULL,FALSE);
               break;

            case MENU_CLEAR:
               /* if running, stop before clearing */
               if(pLife->Run)
                  SendMessage(hWnd,WM_COMMAND,MENU_RUN,0L);
               for(iXCell = 0; iXCell < MAX_XCELL; iXCell++) {
                  for(iYCell = 0; iYCell < MAX_YCELL; iYCell++) {
                     pLife->Matrix[iXCell][iYCell] = DEAD;
                     Scratch[iXCell][iYCell] = DEAD;
                  }
               }
               InvalidateRect(hWnd,NULL,TRUE);
               break;

            case MENU_GRID:
               if(pLife->Grid) {
                  ModifyMenu(hMenuFrame,MENU_GRID,MF_BYCOMMAND,MENU_GRID,
                             "&Gridlines On\t^G");
                  pLife->Grid = FALSE;
               }
               else {
                  ModifyMenu(hMenuFrame,MENU_GRID,MF_BYCOMMAND,MENU_GRID,
                             "&Gridlines Off\t^G");
                  pLife->Grid = TRUE;
               }
               InvalidateRect(hWnd,NULL,TRUE);
               break;

            case MENU_TIMER:
               lpProc = MakeProcInstance((FARPROC) TimerEditDlgProc,hInst);
               DialogBox(hInst,"TIMERDLGBOX",hWnd,lpProc);
               FreeProcInstance(lpProc);
               /* if already running, update the timer */
               if(wCycleTimer) {
                  KillTimer(hWnd,CYCLE_TIMER);
	               if((wCycleTimer =
                     SetTimer(hWnd,CYCLE_TIMER,iCycleTime,NULL)) == 0) {
                     MessageBox(hWnd,"No More System Timers!",
                        NULL,MB_OK | MB_ICONEXCLAMATION);
                     break;
                  }
               }
               break;

            case MENU_SMALL:
               CheckMenuItem(hMenuFrame,MENU_SMALL,MF_BYCOMMAND | MF_CHECKED);
               CheckMenuItem(hMenuFrame,MENU_NORM,MF_BYCOMMAND | MF_UNCHECKED);
               CheckMenuItem(hMenuFrame,MENU_LARGE,MF_BYCOMMAND | MF_UNCHECKED);
               pLife->CellX = iSmall;
               pLife->CellY = iSmall;
               UpdateScrollBarRange(hWnd);
               InvalidateRect(hWnd,NULL,TRUE);
               break;

            case MENU_NORM:
               CheckMenuItem(hMenuFrame,MENU_SMALL,MF_BYCOMMAND | MF_UNCHECKED);
               CheckMenuItem(hMenuFrame,MENU_NORM,MF_BYCOMMAND | MF_CHECKED);
               CheckMenuItem(hMenuFrame,MENU_LARGE,MF_BYCOMMAND | MF_UNCHECKED);
               pLife->CellX = iNorm;
               pLife->CellY = iNorm;
               UpdateScrollBarRange(hWnd);
               InvalidateRect(hWnd,NULL,TRUE);
               break;

            case MENU_LARGE:
               CheckMenuItem(hMenuFrame,MENU_SMALL,MF_BYCOMMAND | MF_UNCHECKED);
               CheckMenuItem(hMenuFrame,MENU_NORM,MF_BYCOMMAND | MF_UNCHECKED);
               CheckMenuItem(hMenuFrame,MENU_LARGE,MF_BYCOMMAND | MF_CHECKED);
               pLife->CellX = iLarge;
               pLife->CellY = iLarge;
               UpdateScrollBarRange(hWnd);
               InvalidateRect(hWnd,NULL,TRUE);
               break;

            case MENU_ABOUT:
               lpProc = MakeProcInstance((FARPROC) AboutDlgProc,hInst);
               DialogBox(hInst,"ABOUTBOX",hWnd,lpProc);
               FreeProcInstance(lpProc);
               break;
         }
         break;

      case WM_TIMER:
         hDC = GetDC(hWnd);
         CycleMatrix(hDC);
         ReleaseDC(hWnd,hDC);
         if(!bIconic)
            InvalidateRect(hWnd,NULL,FALSE);
         break;

      case WM_LBUTTONDOWN:
         iXDown = -1;
         iYDown = -1;
         bMouseDown = TRUE;
         hMouseMoveDC = GetDC(hWnd);
         GetClientRect(hWnd,&rect);
         SelectObject(hMouseMoveDC,GetStockObject(WHITE_PEN));
         break;

      case WM_LBUTTONUP:
         /* force draw at current position */
         SendMessage(hWnd,WM_MOUSEMOVE,wParam,lParam);
         if(bMouseDown) {
            bMouseDown = FALSE;
            SelectObject(hMouseMoveDC,GetStockObject(BLACK_PEN));
            ReleaseDC(hWnd,hMouseMoveDC);
         }
         break;

      case WM_MOUSEMOVE:
         if(bMouseDown) {
            wXMouse = LOWORD(lParam);
            wYMouse = HIWORD(lParam);
            /* translate mouse position into cell coordinates */
            iXCell = (int)wXMouse / pLife->CellX;
            iYCell = (int)wYMouse / pLife->CellY;
            if((iXCell != iXDown) || (iYCell != iYDown)) {
               iXDown = iXCell;
               iYDown = iYCell;
               ucState = (UCHAR)pLife->Matrix[iXCell+pLife->TopCellX]
                                             [iYCell+pLife->TopCellY];
               ucState = (UCHAR)((ucState & ALIVE) ? DEAD : ALIVE | CHILD);
               pLife->Matrix[iXCell+pLife->TopCellX]
                            [iYCell+pLife->TopCellY] = ucState;
               DrawCell(hMouseMoveDC,iXCell,iYCell,ucState);
            }
         }
         break;

      case WM_NCMOUSEMOVE:
         /* look to see if the mouse is outside the client area, if so and
          * the mouse is being captured then release it
          */
         if(bMouseDown) {
            bMouseDown = FALSE;
            SelectObject(hMouseMoveDC,GetStockObject(BLACK_PEN));
            ReleaseDC(hWnd,hMouseMoveDC);
         }
         break;

      case WM_VSCROLL:
         switch(wParam) {
            case SB_LINEUP:
               pLife->TopCellY -= 1;
               break;

            case SB_PAGEUP:
               pLife->TopCellY -= pLife->CellsPerPageY;
               break;

            case SB_LINEDOWN:
               pLife->TopCellY += 1;
               break;

            case SB_PAGEDOWN:
               pLife->TopCellY += pLife->CellsPerPageY;
               break;

            case SB_THUMBPOSITION:
               pLife->TopCellY = LOWORD(lParam);
               break;

            default:
               break;
         }
         pLife->TopCellY = max(1,min(pLife->TopCellY,MAX_YCELL -
                           pLife->CellsPerPageY));
         if(pLife->TopCellY != GetScrollPos(hWnd,SB_VERT)) {
	         iXDown = -1;
   	      iYDown = -1;
            SetScrollPos(hWnd,SB_VERT,pLife->TopCellY,TRUE);
            InvalidateRect(hWnd,NULL,TRUE);
         }
         break;

      case WM_HSCROLL:
         switch(wParam) {
            case SB_LINEUP:
               pLife->TopCellX -= 1;
               break;

            case SB_PAGEUP:
               pLife->TopCellX -= pLife->CellsPerPageX;
               break;

            case SB_LINEDOWN:
               pLife->TopCellX += 1;
               break;

            case SB_PAGEDOWN:
               pLife->TopCellX += pLife->CellsPerPageX;
               break;

            case SB_THUMBPOSITION:
               pLife->TopCellX = LOWORD(lParam);
               break;

            default:
               break;
         }
         pLife->TopCellX = max(1,min(pLife->TopCellX,MAX_XCELL -
                           pLife->CellsPerPageX));
         if(pLife->TopCellX != GetScrollPos(hWnd,SB_HORZ)) {
	         iXDown = -1;
   	      iYDown = -1;
            SetScrollPos(hWnd,SB_HORZ,pLife->TopCellX,TRUE);
            InvalidateRect(hWnd,NULL,TRUE);
         }
         break;

      case WM_KEYDOWN:
         switch(wParam) {
            case VK_PRIOR:
               SendMessage(hWnd,WM_VSCROLL,SB_PAGEUP,0L);
               break;

            case VK_NEXT:
               SendMessage(hWnd,WM_VSCROLL,SB_PAGEDOWN,0L);
               break;

            case VK_UP:
               SendMessage(hWnd,WM_VSCROLL,SB_LINEUP,0L);
               break;

            case VK_DOWN:
               SendMessage(hWnd,WM_VSCROLL,SB_LINEDOWN,0L);
               break;

            case VK_LEFT:
               SendMessage(hWnd,WM_HSCROLL,SB_PAGEUP,0L);
               break;

            case VK_RIGHT:
               SendMessage(hWnd,WM_HSCROLL,SB_PAGEDOWN,0L);
               break;
         }
         break;

      case WM_SIZE:
         UpdateScrollBarRange(hWnd);
         break;

      case WM_PAINT:
         hDC = BeginPaint(hWnd,&ps);
         if(pLife->Grid)
            DrawGridLines(hDC,&ps.rcPaint);
         DrawMatrix(hDC);
         EndPaint(hWnd,&ps);
         break;

      case WM_SYSCOMMAND:
         switch(wParam) {
            case SC_MINIMIZE:
               bIconic = TRUE;
               break;

            case SC_MAXIMIZE:
            case SC_RESTORE:
               bIconic = FALSE;
               break;
         }
         return DefWindowProc(hWnd, iMessage, wParam, lParam);
         break;

      case WM_CREATE:
         hBluePen = CreatePen(PS_SOLID,1,RGB(0,0,128));
         hGreenPen = CreatePen(PS_SOLID,1,RGB(0,128,0));
         hCyanPen = CreatePen(PS_SOLID,1,RGB(0,128,128));
         hRedPen = CreatePen(PS_SOLID,1,RGB(128,0,0));
         hMagentaPen = CreatePen(PS_SOLID,1,RGB(128,0,128));
         hBrownPen = CreatePen(PS_SOLID,1,RGB(128,128,0));
         hGrayPen = CreatePen(PS_SOLID,1,RGB(192,192,192));
         hLightRedPen = CreatePen(PS_SOLID,1,RGB(255,0,0));
         hWhitePen = CreatePen(PS_SOLID,1,RGB(255,255,255));
         hBlueBrush = CreateSolidBrush(RGB(0,0,128));
         hGreenBrush = CreateSolidBrush(RGB(0,128,0));
         hCyanBrush = CreateSolidBrush(RGB(0,128,128));
         hRedBrush = CreateSolidBrush(RGB(128,0,0));
         hMagentaBrush = CreateSolidBrush(RGB(128,0,128));
         hBrownBrush = CreateSolidBrush(RGB(128,128,0));
         hGrayBrush = CreateSolidBrush(RGB(192,192,192));
         hLightRedBrush = CreateSolidBrush(RGB(255,0,0));
         hWhiteBrush = CreateSolidBrush(RGB(255,255,255));
	      srand((unsigned)time(NULL));
         break;

      case WM_CLOSE:
         if(wCycleTimer) {
            KillTimer(hWnd,CYCLE_TIMER);
            wCycleTimer = 0;
         }
         if(hMatrix != NULL) {
            LocalUnlock(hMatrix);
            LocalFree(hMatrix);
         }
         DeleteObject(hBluePen);
         DeleteObject(hGreenPen);
         DeleteObject(hCyanPen);
         DeleteObject(hRedPen);
         DeleteObject(hMagentaPen);
         DeleteObject(hBrownPen);
         DeleteObject(hGrayPen);
         DeleteObject(hWhitePen);
         DeleteObject(hBlueBrush);
         DeleteObject(hGreenBrush);
         DeleteObject(hCyanBrush);
         DeleteObject(hRedBrush);
         DeleteObject(hMagentaBrush);
         DeleteObject(hBrownBrush);
         DeleteObject(hGrayBrush);
         DeleteObject(hWhiteBrush);
         PostQuitMessage(0);
         break;

      default:
         return DefWindowProc(hWnd, iMessage, wParam, lParam);
   }
   return 0L;
}

/****************************************************************************
*  int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance,
*                     LPSTR lpszCmdLine,int nCmdShow)
*
*  Description:   Life Main Window Function
*
*  Input:         hInstance   	- Window instance handle
*                 hPrevInstance 	- Previous Instance Handle
*                 lpszCmdLine 	- Window Activation Parameters (none req.)
*                 nCmdShow    	- Show Window or Icon command
*                 Called externally from WINDOWS
*
*  Output:        Handles Windows message loop,terminates to Windows
*****************************************************************************/
int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance,LPSTR lpszCmdLine,
                   int nCmdShow)
{
   HWND     hWnd;
   MSG      msg;
   WNDCLASS wc;

   hInst = hInstance;
   if(!hPrevInstance)
   {
      wc.style          = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc    = WndProc;
      wc.cbClsExtra     = 0;
      wc.cbWndExtra     = 0;
      wc.hInstance      = hInstance;
      wc.hIcon          = LoadIcon(hInstance,"LIFEICON");
      wc.hCursor        = LoadCursor(hInstance,"HAND");
      wc.hbrBackground  = GetStockObject(WHITE_BRUSH);
      wc.lpszMenuName   = "MainMenu";
      wc.lpszClassName  = CLASSNAME;

      if(!RegisterClass(&wc)) {
         MessageBox(NULL, "Initialization Error!", APPNAME,
                    MB_OK | MB_ICONHAND | MB_SYSTEMMODAL);
         return TRUE;
      }
   }
   hWnd = CreateWindow(CLASSNAME,             /* Window class name          */
                       APPNAME,               /* window caption             */
                       WS_OVERLAPPEDWINDOW | WS_VSCROLL |
                       WS_HSCROLL,            /* window style               */
                       CW_USEDEFAULT,         /* initial x (horiz) position */
                       CW_USEDEFAULT,         /* initial y (vert) position  */
                       CW_USEDEFAULT,         /* initial x size             */
                       CW_USEDEFAULT,         /* initial y size             */
                       NULL,                  /* parent window handle       */
                       NULL,                  /* window menu handle         */
                       hInstance,             /* program instance handle    */
                       NULL);                 /* create parameters          */


   /* initialize variables */
   hWndFrame = hWnd;                          /* global parent window       */
   hMenuFrame = GetMenu(hWnd);                /* global menu handle         */
   hAccelFrame = LoadAccelerators(hInstance,"LifeKeys"); /* key accelerator */

   /* initialize the Life cell structure */
   if(!InitLife())
      return TRUE;

   ShowWindow(hWnd,nCmdShow);                 /* shows the window           */

   /* message loop */
   while(GetMessage(&msg,NULL,0,0)) {
      if(!TranslateAccelerator(hWnd,hAccelFrame,&msg)) {
	      TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   }
   return msg.wParam;
}
