/************************************************************************** * * Copyright (c) 1998-2000, Microsoft Corp. All Rights Reserved. * * Module Name: * * life.cpp * * Abstract: * * Conway's game of Life using GDI+. * * Revision History: * * 9/12/2000 asecchia - Created it. * ***************************************************************************/ #include "life.hpp" #include // This needs to be set on the command line. See the sources file. // Turning this switch on causes the screen saver to run in a window from // the command line, making debuggin a lot easier. #ifdef STANDALONE_DEBUG HINSTANCE hMainInstance; HWND ghwndMain; HBRUSH ghbrWhite; TCHAR szIniFile[MAXFILELEN]; #else extern HINSTANCE hMainInstance; /* screen saver instance handle */ #endif // ASSERT code. #if DBG #define ASSERT(a) if(!(a)) { DebugBreak();} #else #define ASSERT(a) #endif // explicit unreferenced parameter. #define UNREF(a) (a); #define CBSIZE 40 class CachedImageArray { CachedBitmap *cbArray[CBSIZE]; int num; public: CachedImageArray() { num = 0; } // Cache an entry. bool Add(CachedBitmap *cb) { if(num>=CBSIZE) { return false; } cbArray[num] = cb; num++; return true; } int Size() {return num;} CachedBitmap *operator[] (int i) { if( (i<0) || (i>=num) ) { return NULL; } return cbArray[i]; } // Throw everything away. void Dispose() { for(int i=0; i 0 ); } void LoadState() { // Retrieve the application name from the RC file. LoadStringW( hMainInstance, idsAppName, szAppName, 40 ); // Retrieve the .ini file name from the RC file. LoadStringW( hMainInstance, idsIniFile, szIniFile, MAXFILELEN ); // Retrieve any redraw speed data from the registry. nSpeed = GetPrivateProfileIntW( szAppName, L"Redraw Speed", SPEED_DEF, szIniFile ); // Only allow defined values. nSpeed = max(nSpeed, SPEED_MIN); nSpeed = min(nSpeed, SPEED_MAX); // Retrieve any tile size from the registry. nTileSize = GetPrivateProfileIntW( szAppName, L"Tile Size", TILESIZE_DEF, szIniFile ); // Only allow defined values. nTileSize = max(nTileSize, TILESIZE_MIN); nTileSize = min(nTileSize, TILESIZE_MAX); // Get the directory name. NULL if failed. GetPrivateProfileStringW( szAppName, L"Image Path", L"", gDirPath, MAX_PATH, szIniFile ); } void SaveState() { WCHAR szTemp[20]; // Write out the registry setting for the speed. wsprintf(szTemp, L"%ld", nSpeed); WritePrivateProfileStringW( szAppName, L"Redraw Speed", szTemp, szIniFile ); // Write out the registry setting for the tile size. wsprintf(szTemp, L"%ld", nTileSize); WritePrivateProfileStringW( szAppName, L"Tile Size", szTemp, szIniFile ); // Set the directory name. NULL if failed. WritePrivateProfileStringW( szAppName, L"Image Path", gDirPath, szIniFile ); } void ClearOffscreenDIB() { if (gOffscreenInfo.hdc) { PatBlt( gOffscreenInfo.hdc, 0, 0, gOffscreenInfo.bmi.bmiHeader.biWidth, gOffscreenInfo.bmi.bmiHeader.biHeight, BLACKNESS ); } } VOID CreateOffscreenDIB(HDC hdc, INT width, INT height) { gOffscreenInfo.bmi.bmiHeader.biSize = sizeof(gOffscreenInfo.bmi.bmiHeader); gOffscreenInfo.bmi.bmiHeader.biWidth = width; gOffscreenInfo.bmi.bmiHeader.biHeight = height; gOffscreenInfo.bmi.bmiHeader.biPlanes = 1; gOffscreenInfo.bmi.bmiHeader.biBitCount = 32; gOffscreenInfo.bmi.bmiHeader.biCompression = BI_RGB; gOffscreenInfo.hbmpOffscreen = CreateDIBSection( hdc, &gOffscreenInfo.bmi, DIB_RGB_COLORS, &gOffscreenInfo.pvBits, NULL, 0 ); if (gOffscreenInfo.hbmpOffscreen) { gOffscreenInfo.hdc = CreateCompatibleDC(hdc); if (gOffscreenInfo.hdc) { gOffscreenInfo.hbmpOld = (HBITMAP)SelectObject( gOffscreenInfo.hdc, gOffscreenInfo.hbmpOffscreen ); ClearOffscreenDIB(); } } } BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hSpeed; // handle to the speed scrollbar. switch(message) { case WM_INITDIALOG: // Load the global state. LoadState(); // Initialize the speed scroll bar hSpeed = GetDlgItem(hDlg, ID_SPEED); SetScrollRange(hSpeed, SB_CTL, SPEED_MIN, SPEED_MAX, FALSE); SetScrollPos(hSpeed, SB_CTL, nSpeed, TRUE); // Initialize the tile size radio buttons CheckRadioButton(hDlg, IDC_RADIOTYPE1, IDC_RADIOTYPE5, IDC_RADIOTYPE1+(TILESIZE_MAX-nTileSize)); return TRUE; case WM_HSCROLL: // Process the speed control scrollbar. switch (LOWORD(wParam)) { case SB_PAGEUP: --nSpeed; break; case SB_LINEUP: --nSpeed; break; case SB_PAGEDOWN: ++nSpeed; break; case SB_LINEDOWN: ++nSpeed; break; case SB_THUMBPOSITION: nSpeed = HIWORD(wParam); break; case SB_BOTTOM: nSpeed = SPEED_MIN; break; case SB_TOP: nSpeed = SPEED_MAX; break; case SB_THUMBTRACK: case SB_ENDSCROLL: return TRUE; break; } nSpeed = max(nSpeed, SPEED_MIN); nSpeed = min(nSpeed, SPEED_MAX); SetScrollPos((HWND) lParam, SB_CTL, nSpeed, TRUE); break; case WM_COMMAND: switch(LOWORD(wParam)) { case ID_DIR: // Do the COM thing for the SHBrowseForFolder dialog. CoInitialize(NULL); IMalloc *piMalloc; if(SUCCEEDED(SHGetMalloc(&piMalloc))) { BROWSEINFOW bi; memset(&bi, 0, sizeof(bi)); bi.hwndOwner = hDlg; bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_EDITBOX; bi.lpszTitle = L"Select image directory:"; WCHAR wszPath[MAX_PATH]; bi.pszDisplayName = wszPath; LPITEMIDLIST lpiList = SHBrowseForFolderW(&bi); if(lpiList) { if(SHGetPathFromIDListW(lpiList, wszPath)) { wcscpy(gDirPath, wszPath); } piMalloc->Free(lpiList); } piMalloc->Release(); } CoUninitialize(); break; case ID_OK: // Tile size radio buttons. if (IsDlgButtonChecked(hDlg, IDC_RADIOTYPE1)) { nTileSize = 4; } else if (IsDlgButtonChecked(hDlg, IDC_RADIOTYPE2)) { nTileSize = 3; } else if (IsDlgButtonChecked(hDlg, IDC_RADIOTYPE3)) { nTileSize = 2; } else if (IsDlgButtonChecked(hDlg, IDC_RADIOTYPE4)) { nTileSize = 1; } else { nTileSize = 0; // smallest } SaveState(); // intentionally fall through to exit. case ID_CANCEL: EndDialog(hDlg, LOWORD(wParam) == IDOK); return TRUE; } } return FALSE; } BOOL WINAPI RegisterDialogClasses( HANDLE hInst ) { return TRUE; UNREF(hInst); } LRESULT WINAPI ScreenSaverProcW (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HDC hdc; // device-context handle static RECT rc; // RECT structure static UINT_PTR uTimer; // timer identifier static bool GdiplusInitialized = false; static ULONG_PTR gpToken; GdiplusStartupInput sti; switch(message) { case WM_CREATE: // Initialize GDI+ if (GdiplusStartup(&gpToken, &sti, NULL) == Ok) { GdiplusInitialized = true; } // Only do work if we successfully initialized. if(GdiplusInitialized) { // Retrieve the application name from the .rc file. LoadString(hMainInstance, idsAppName, szAppName, 40); // Initialize the global state. GetClientRect (hwnd, &rc); LoadState(); switch(nTileSize) { // 1x1 pixel case 0: gSizeX = 1; gSizeY = 1; break; // Aspect ratio of 4x3, for pictures. case 1: gSizeX = 16; gSizeY = 12; break; case 2: gSizeX = 32; gSizeY = 24; break; case 3: gSizeX = 64; gSizeY = 48; break; case 4: gSizeX = 96; gSizeY = 72; break; } ghFile = 0; gGenerations = 400; gCurrentGeneration = 0; gWidth = (rc.right - rc.left + 1)/gSizeX; gHeight = (rc.bottom - rc.top + 1)/gSizeY; gLifeMatrix = (INT *)malloc(sizeof(INT)*gWidth*gHeight); gTempMatrix = (INT *)malloc(sizeof(INT)*gWidth*gHeight); if(nTileSize == 0) { // 1x1 tilesize case. CreateOffscreenDIB( hdc, rc.right - rc.left + 1, rc.bottom - rc.top + 1 ); red = rand() % 255; green = rand() % 255; blue = min(255, 512 - (red + green)); ri = (rand() % 3) - 1; // 1, 0 or -1 bi = (rand() % 3) - 1; // 1, 0 or -1 gi = (rand() % 3) - 1; // 1, 0 or -1 } else { // Image case. CachedImages = new CachedImageArray(); } maxImage = CBSIZE; currentImage = 0; // initial number // Set a timer for the screen saver window uTimer = SetTimer(hwnd, 1, 1000, NULL); srand( (unsigned)GetTickCount() ); } break; case WM_ERASEBKGND: // The WM_ERASEBKGND message is issued before the // WM_TIMER message, allowing the screen saver to // paint the background as appropriate. break; case WM_TIMER: // Only do work if we successfully initialized. if(GdiplusInitialized) { if (uTimer) { KillTimer(hwnd, uTimer); } hdc = GetDC(hwnd); GetClientRect(hwnd, &rc); DrawLifeIteration(hdc); uTimer = SetTimer(hwnd, 1, nSpeed*10, NULL); ReleaseDC(hwnd,hdc); } break; case WM_DESTROY: // When the WM_DESTROY message is issued, the screen saver // must destroy any of the timers that were set at WM_CREATE // time. // Only do work if we successfully initialized. if(GdiplusInitialized) { if (uTimer) { KillTimer(hwnd, uTimer); } free(gTempMatrix); free(gLifeMatrix); FindClose(ghFile); delete CachedImages; GdiplusShutdown(gpToken); GdiplusInitialized = false; } break; } // DefScreenSaverProc processes any messages ignored by ScreenSaverProc. #ifdef STANDALONE_DEBUG return DefWindowProc(hwnd, message, wParam, lParam); #else return DefScreenSaverProc(hwnd, message, wParam, lParam); #endif } #define TEMP(x, y) gTempMatrix[ ((x+gWidth) % gWidth) + ((y+gHeight) % gHeight)*gWidth ] #define LIFE(x, y) gLifeMatrix[ ((x+gWidth) % gWidth) + ((y+gHeight) % gHeight)*gWidth ] inline bool AliveT(int x, int y) { return (TEMP(x, y) & 0x1); } inline bool AliveL(int x, int y) { return (LIFE(x, y) & 0x1); } inline INT CountT(int x, int y) { return (TEMP(x, y) >> 1); } inline void NewCellL(INT x, INT y) { ASSERT(!AliveL(x, y)); // update current cell LIFE(x, y) += 1; // update neighbour counts. LIFE(x-1, y-1) += 2; LIFE(x-1, y ) += 2; LIFE(x-1, y+1) += 2; LIFE(x , y-1) += 2; LIFE(x , y+1) += 2; LIFE(x+1, y-1) += 2; LIFE(x+1, y ) += 2; LIFE(x+1, y+1) += 2; } inline void NewCellL_NoWrap(INT index) { ASSERT(! (gLifeMatrix[index] & 0x1) ); // update current cell gLifeMatrix[index] += 1; // update neighbour counts. gLifeMatrix[index - 1 - gWidth] += 2; gLifeMatrix[index - 1 ] += 2; gLifeMatrix[index - 1 + gWidth] += 2; gLifeMatrix[index - gWidth] += 2; gLifeMatrix[index + gWidth] += 2; gLifeMatrix[index + 1 - gWidth] += 2; gLifeMatrix[index + 1 ] += 2; gLifeMatrix[index + 1 + gWidth] += 2; } inline void KillCellL(INT x, INT y) { ASSERT(AliveL(x, y)); // update current cell LIFE(x, y) -= 1; // update neighbour counts. LIFE(x-1, y-1) -= 2; LIFE(x-1, y ) -= 2; LIFE(x-1, y+1) -= 2; LIFE(x , y-1) -= 2; LIFE(x , y+1) -= 2; LIFE(x+1, y-1) -= 2; LIFE(x+1, y ) -= 2; LIFE(x+1, y+1) -= 2; } inline void KillCellL_NoWrap(INT index) { ASSERT(gLifeMatrix[index] & 0x1); // update current cell gLifeMatrix[index] -= 1; // update neighbour counts. gLifeMatrix[index - 1 - gWidth] -= 2; gLifeMatrix[index - 1 ] -= 2; gLifeMatrix[index - 1 + gWidth] -= 2; gLifeMatrix[index - gWidth] -= 2; gLifeMatrix[index + gWidth] -= 2; gLifeMatrix[index + 1 - gWidth] -= 2; gLifeMatrix[index + 1 ] -= 2; gLifeMatrix[index + 1 + gWidth] -= 2; } VOID InitLifeMatrix() { memset(gLifeMatrix, 0, sizeof(INT)*gWidth*gHeight); if(nTileSize == 0) { ClearOffscreenDIB(); } if((rand()%2 == 0) || (gWidthGetLastStatus() != Ok) ); return bmp; } VOID InitialPaintPicture(Graphics *g, CachedBitmap *cb) { SolidBrush OffBrush(Color(0xff000000)); for(int x=0; xDrawCachedBitmap( cb, x*gSizeX, y*gSizeY ); } else { // we should really use a bitblt for this. g->FillRectangle( &OffBrush, x*gSizeX, y*gSizeY, gSizeX, gSizeY ); } } } } VOID InitialPaintPixel() { ASSERT(nTileSize == 0); DWORD *pixel = (DWORD*)(gOffscreenInfo.pvBits); ASSERT(pixel != NULL); for(int x=0; xSetInterpolationMode(InterpolationModeHighQualityBicubic); g->DrawImage( bmp, Rect(0,0,gSizeX,gSizeY), 0, 0, bmp->GetWidth(), bmp->GetHeight(), UnitPixel ); // Create the CachedBitmap from the tile. CachedBitmap *cb = new CachedBitmap(tileBmp, gfxMain); // clean up. delete g; delete tileBmp; return cb; } VOID IteratePixelGeneration() { DWORD *pixel = (DWORD*)(gOffscreenInfo.pvBits); INT count; INT index = 1+gWidth; for(int y=1; y> 1; // if the cell is alive and its neighbour count // is not 2 or 3, it dies. if( (gTempMatrix[index] & 0x1) && ((count<2) || (count>3)) ) { // Kill the cell - overcrowding or not enough support. KillCellL_NoWrap(index); pixel[index] = 0; } } } index++; } // skip the wrap boundaries. index += 2; } index = 0; for(int y=0; y> 1; // if the cell is alive and its neighbour count // is not 2 or 3, it dies. if( (gTempMatrix[index] & 0x1) && ((count<2) || (count>3)) ) { // Kill the cell - overcrowding or not enough support. KillCellL(0, y); pixel[index] = 0; } } } // right vertical edge index += gWidth-1; if(gTempMatrix[index] != 0) { // If the cell is not alive and it's neighbour count // is exactly 3, it is born. if( gTempMatrix[index] == 6 ) { // A new cell is born into an empty square. NewCellL(gWidth-1, y); pixel[index] = gGenerationColor; } else { count = gTempMatrix[index] >> 1; // if the cell is alive and its neighbour count // is not 2 or 3, it dies. if( (gTempMatrix[index] & 0x1) && ((count<2) || (count>3)) ) { // Kill the cell - overcrowding or not enough support. KillCellL(gWidth-1, y); pixel[index] = 0; } } } // next scanline. index++; } index = 1; INT index2 = index + (gHeight-1)*gWidth; for(int x=1; x> 1; // if the cell is alive and its neighbour count // is not 2 or 3, it dies. if( (gTempMatrix[index] & 0x1) && ((count<2) || (count>3)) ) { // Kill the cell - overcrowding or not enough support. KillCellL(x, 0); pixel[index] = 0; } } } index++; // bottom edge if(gTempMatrix[index2] != 0) { // If the cell is not alive and it's neighbour count // is exactly 3, it is born. if( gTempMatrix[index2] == 6 ) { // A new cell is born into an empty square. NewCellL(x, gHeight-1); pixel[index2] = gGenerationColor; } else { count = gTempMatrix[index2] >> 1; // if the cell is alive and its neighbour count // is not 2 or 3, it dies. if( (gTempMatrix[index2] & 0x1) && ((count<2) || (count>3)) ) { // Kill the cell - overcrowding or not enough support. KillCellL(x, gHeight-1); pixel[index2] = 0; } } } // next pixel. index2++; } } VOID IteratePictureGeneration(Graphics &g, CachedBitmap *cb) { SolidBrush OffBrush(Color(0xff000000)); INT count; INT *cell = gTempMatrix; for(int y=0; y> 1; // if the cell is alive and its neighbour count // is not 2 or 3, it dies. if( (*cell & 0x1) && ((count<2) || (count>3)) ) { // Kill the cell - overcrowding or not enough support. KillCellL(x, y); g.FillRectangle( &OffBrush, x*gSizeX, y*gSizeY, gSizeX, gSizeY ); } } } cell++; } } } VOID RandomizeColor() { if(rand() % 200 == 0) { ri = (rand() % 3) - 1; // 1, 0 or -1 } if(rand() % 200 == 0) { gi = (rand() % 3) - 1; // 1, 0 or -1 } if(rand() % 200 == 0) { bi = (rand() % 3) - 1; // 1, 0 or -1 } if((red < 100) && (green < 100) && (blue < 100)) { if(red > green && red > blue) { ri = 1; } else if (green > blue) { gi = 1; } else { bi = 1; } } // bounce off the extrema. if(red == 0) { ri = 1; } if(red == 255) { ri = -1; } if(green == 0) { gi = 1; } if(green == 255) { gi = -1; } if(blue == 0) { bi = 1; } if(blue == 255) { bi = -1; } red += ri; green += gi; blue += bi; } VOID DrawLifeIteration(HDC hdc) { // Are we initialized yet? if(!gLifeMatrix || !gTempMatrix) { return; } Graphics g(hdc); g.SetSmoothingMode(SmoothingModeNone); Bitmap *bmp = NULL; CachedBitmap *cb = NULL; // currentImage should never be larger than CBSIZE at this point. ASSERT(currentImage < CBSIZE); if(nTileSize==0) { // cycle color. RandomizeColor(); gGenerationColor = RGB(red, green, blue); } else { // Fetch bitmaps from the image directory. if(currentImage >= CachedImages->Size()) { // We haven't filled up the cache yet. Keep opening images. bmp = OpenBitmap(); } // Did we get a new bitmap? if(bmp) { cb = MakeCachedBitmapEntry(bmp, &g); if(cb) { // Put it in the cache. CachedImages->Add(cb); currentImage++; } delete bmp; bmp = NULL; } else { cb = (*CachedImages)[currentImage]; currentImage++; } if( (currentImage >= CBSIZE) || (currentImage >= maxImage) ) { currentImage = 0; } if(!cb) { // we failed to get an image tile. return; } } // update the generation and see if we need to do the first generation. //gCurrentGeneration--; if(gCurrentGeneration <= 0) { // gCurrentGeneration = gGenerations; gCurrentGeneration++; InitLifeMatrix(); if(nTileSize == 0) { InitialPaintPixel(); } else { InitialPaintPicture(&g, cb); } goto Done; } gCurrentGeneration++; // Make a copy of the life matrix. memcpy(gTempMatrix, gLifeMatrix, sizeof(INT)*gWidth*gHeight); if(nTileSize==0) { IteratePixelGeneration(); ASSERT(gSizeX == 1); ASSERT(gSizeY == 1); StretchBlt( hdc, 0, 0, gWidth, gHeight, gOffscreenInfo.hdc, 0, 0, gWidth, gHeight, SRCCOPY ); } else { IteratePictureGeneration(g, cb); } // 5% mutation. /* if(((float)(rand())/RAND_MAX) < 0.05f) { int x = rand()*gWidth/RAND_MAX; int y = rand()*gHeight/RAND_MAX; if(AliveL(x, y)) { KillCellL(x, y); g.FillRectangle( &OffBrush, x*gSizeX, y*gSizeY, gSizeX, gSizeY ); } else { NewCellL(x, y); g.DrawCachedBitmap( cb, x*gSizeX, y*gSizeY ); } } */ Done: ; } #ifdef STANDALONE_DEBUG LONG_PTR lMainWindowProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch(message) { // Handle the destroy message. case WM_DESTROY: DeleteObject(ghbrWhite); PostQuitMessage(0); break; } // Hook into the screen saver windproc. return(ScreenSaverProcW(hwnd, message, wParam, lParam)); } BOOL bInitApp(VOID) { WNDCLASS wc; // not quite so white background brush. ghbrWhite = CreateSolidBrush(RGB(0xFF,0xFF,0xFF)); wc.style = 0; wc.lpfnWndProc = lMainWindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hMainInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = ghbrWhite; wc.lpszMenuName = L"MainMenu"; wc.lpszClassName = L"TestClass"; if(!RegisterClass(&wc)) { return FALSE; } ghwndMain = CreateWindowExW( 0, L"TestClass", L"Win32 Test", WS_OVERLAPPED | WS_CAPTION | WS_BORDER | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_VISIBLE | WS_SYSMENU, 80, 70, 800, 600, NULL, NULL, hMainInstance, NULL ); if (ghwndMain == NULL) { return(FALSE); } SetFocus(ghwndMain); return TRUE; } void _cdecl main( INT argc, PCHAR argv[] ) { MSG msg; hMainInstance = GetModuleHandle(NULL); if(!bInitApp()) {return;} while(GetMessage (&msg, NULL, 0, 0)) { if((ghwndMain == 0) || !IsDialogMessage(ghwndMain, &msg)) { TranslateMessage(&msg) ; DispatchMessage(&msg) ; } } return; UNREF(argc); UNREF(argv); } #endif