I've implemented Conway's Game of Life using the raw Windows API and GDI as an exercise. I'm wondering if there is room for improvement, though.
Beware, large amount of code following:
CGOL.h:
#include <Windows.h>
#include <windowsx.h>
#include <stdlib.h>
#include <time.h>
#include <sal.h>
#define IDT_TIMER1 1001
#define EXTERN extern // OCD with the caps stuff
#define GRIDSIZE 80
#define CELLSIZE 10
EXTERN CONST WCHAR g_wszClassName[];
EXTERN INT g_aGrid[GRIDSIZE][GRIDSIZE];
EXTERN INT g_aUpdate[GRIDSIZE][GRIDSIZE];
EXTERN HDC g_hDC;
EXTERN HBRUSH g_hbrCell;
EXTERN HBRUSH g_hbrBackground;
VOID WINAPI OnClose(_In_ HWND hWnd);
VOID WINAPI OnDestroy(_In_ HWND hWnd);
BOOL WINAPI OnCreate(_In_ HWND hWnd, _In_ LPCREATESTRUCTW lpCreateStruct);
ATOM WINAPI RegisterWCEX(_In_ HINSTANCE hInstance);
VOID CALLBACK TimerProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ UINT_PTR idEvent, _In_ DWORD dwTime);
LRESULT CALLBACK WindowProc(_In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam);
INT APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_z_ LPWSTR lpCmdLine, _In_ INT nShowCmd);
onclose.c:
#include "CGOL.h"
VOID WINAPI OnClose(
_In_ HWND hWnd
)
{
KillTimer(hWnd, IDT_TIMER1);
DestroyWindow(hWnd);
}
oncreate.c:
#include "CGOL.h"
HBRUSH g_hbrCell;
HBRUSH g_hbrBackground;
INT g_aGrid[GRIDSIZE][GRIDSIZE];
INT g_aUpdate[GRIDSIZE][GRIDSIZE];
BOOL WINAPI OnCreate(
_In_ HWND hWnd,
_In_ LPCREATESTRUCTW lpCreateStruct
)
{
int x, y;
g_hbrCell = CreateSolidBrush(RGB(0, 255, 0));
g_hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
srand((UINT)time(NULL));
ZeroMemory(g_aGrid, sizeof(INT) * GRIDSIZE * GRIDSIZE);
ZeroMemory(g_aUpdate, sizeof(INT) * GRIDSIZE * GRIDSIZE);
for (y = 0; y < GRIDSIZE; y++)
{
for (x = 0; x < GRIDSIZE; x++)
{
g_aGrid[y][x] = ((rand()%2)==0);
}
}
for (y = 0; y < GRIDSIZE; y++)
for (x = 0; x < GRIDSIZE; x++)
g_aUpdate[y][x] = g_aGrid[y][x];
g_hDC = GetDC(hWnd);
SetTimer(hWnd, IDT_TIMER1, 100, TimerProc);
return TRUE;
}
ondestroy.c:
#include "CGOL.h"
VOID WINAPI OnDestroy(
_In_ HWND hWnd
)
{
PostQuitMessage(0);
}
registerwcex.c:
#include "CGOL.h"
ATOM WINAPI RegisterWCEX(
_In_ HINSTANCE hInstance
)
{
WNDCLASSEXW wcex;
ZeroMemory(&wcex, sizeof(WNDCLASSEXW));
wcex.cbSize = sizeof(WNDCLASSEXW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.hCursor = LoadCursorW(NULL, IDC_ARROW);
wcex.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wcex.hIconSm = LoadIconW(NULL, IDI_APPLICATION);
wcex.hInstance = hInstance;
wcex.lpfnWndProc = WindowProc;
wcex.lpszClassName = g_wszClassName;
return RegisterClassExW(&wcex);
}
timerproc.c:
#include "CGOL.h"
HDC g_hDC;
VOID CALLBACK TimerProc(
_In_ HWND hWnd,
_In_ UINT uMsg,
_In_ UINT_PTR idEvent,
_In_ DWORD dwTime
)
{
int x, y, nAdj = 0;
SelectObject(g_hDC, g_hbrBackground);
Rectangle(g_hDC, 0, 0, GRIDSIZE * CELLSIZE, GRIDSIZE * CELLSIZE);
SelectObject(g_hDC, g_hbrCell);
for (y = 0; y < GRIDSIZE; y++)
for (x = 0; x < GRIDSIZE; x++)
if (g_aUpdate[y][x] == 1)
Rectangle(g_hDC, x * CELLSIZE, y * CELLSIZE, x * CELLSIZE + CELLSIZE, y * CELLSIZE + CELLSIZE);
for (y = 0; y < GRIDSIZE; y++)
for (x = 0; x < GRIDSIZE; x++)
g_aGrid[y][x] = g_aUpdate[y][x];
for (y = 1; y < GRIDSIZE-1; y++)
{
for (x = 1; x < GRIDSIZE-1; x++)
{
if (g_aGrid[y][x - 1] == 1)
nAdj++;
if (g_aGrid[y - 1][x - 1] == 1)
nAdj++;
if (g_aGrid[y - 1][x] == 1)
nAdj++;
if (g_aGrid[y - 1][x + 1] == 1)
nAdj++;
if (g_aGrid[y][x + 1] == 1)
nAdj++;
if (g_aGrid[y + 1][x + 1] == 1)
nAdj++;
if (g_aGrid[y + 1][x] == 1)
nAdj++;
if (g_aGrid[y + 1][x - 1] == 1)
nAdj++;
if (g_aGrid[y][x] == 0 && nAdj == 3)
g_aUpdate[y][x] = 1;
if (g_aGrid[y][x] == 1)
{
if (nAdj > 3 || nAdj < 2)
g_aUpdate[y][x] = 0;
}
nAdj = 0;
}
}
nAdj = 0;
// EDGES
// LEFT VERTICAL EDGE
for (y = 1; y < GRIDSIZE-1; y++)
{
nAdj = 0;
if (g_aGrid[y + 1][0] == 1)
nAdj++;
if (g_aGrid[y][1] == 1)
nAdj++;
if (g_aGrid[y - 1][0] == 1)
nAdj++;
if (g_aGrid[y + 1][1] == 1)
nAdj++;
if (g_aGrid[y - 1][1] == 1)
nAdj++;
if (nAdj > 3 || nAdj < 2)
g_aUpdate[y][0] = 0;
if (g_aGrid[y][0] == 0 && nAdj == 3)
g_aUpdate[y][0] = 1;
}
// RIGHT VERTICAL EDGE
for (y = 1; y < GRIDSIZE-1; y++)
{
nAdj = 0;
if (g_aGrid[y + 1][GRIDSIZE-1] == 1)
nAdj++;
if (g_aGrid[y][GRIDSIZE-2] == 1)
nAdj++;
if (g_aGrid[y - 1][GRIDSIZE-1] == 1)
nAdj++;
if (g_aGrid[y + 1][GRIDSIZE-2] == 1)
nAdj++;
if (g_aGrid[y - 1][GRIDSIZE-2] == 1)
nAdj++;
if (nAdj > 3 || nAdj < 2)
g_aUpdate[y][GRIDSIZE-1] = 0;
if (g_aGrid[y][GRIDSIZE-1] == 0 && nAdj == 3)
g_aUpdate[y][GRIDSIZE-1] = 1;
}
// TOP HORIZONTAL EDGE
for (x = 1; x < GRIDSIZE-1; x++)
{
nAdj = 0;
if (g_aGrid[0][x + 1] == 1)
nAdj++;
if (g_aGrid[1][x] == 1)
nAdj++;
if (g_aGrid[0][x - 1] == 1)
nAdj++;
if (g_aGrid[1][x + 1] == 1)
nAdj++;
if (g_aGrid[1][x - 1] == 1)
nAdj++;
if (nAdj > 3 || nAdj < 2)
g_aUpdate[0][x] = 0;
if (g_aGrid[0][x] == 0 && nAdj == 3)
g_aUpdate[0][x] = 1;
}
// BOTTOM HORIZONTAL EDGE
for (x = 1; x < GRIDSIZE-1; x++)
{
nAdj = 0;
if (g_aGrid[GRIDSIZE-1][x + 1] == 1)
nAdj++;
if (g_aGrid[GRIDSIZE-2][x] == 1)
nAdj++;
if (g_aGrid[GRIDSIZE-1][x - 1] == 1)
nAdj++;
if (g_aGrid[GRIDSIZE-2][x + 1] == 1)
nAdj++;
if (g_aGrid[GRIDSIZE-2][x - 1] == 1)
nAdj++;
if (nAdj > 3 || nAdj < 2)
g_aUpdate[GRIDSIZE-1][x] = 0;
if (g_aGrid[GRIDSIZE-1][x] == 0 && nAdj == 3)
g_aUpdate[GRIDSIZE-1][x] = 1;
}
nAdj = 0;
// CORNERS
if (g_aGrid[0][0] == 1)
{
if (g_aGrid[0][1] == 1)
nAdj++;
if (g_aGrid[1][0] == 1)
nAdj++;
if (g_aGrid[1][1] == 1)
nAdj++;
if (nAdj < 2)
g_aUpdate[0][0] = 0;
}
nAdj = 0;
if (g_aGrid[0][GRIDSIZE-1] == 1)
{
if (g_aGrid[1][GRIDSIZE-1] == 1)
nAdj++;
if (g_aGrid[0][GRIDSIZE-2] == 1)
nAdj++;
if (g_aGrid[1][GRIDSIZE-2] == 1)
nAdj++;
if (nAdj < 2)
g_aUpdate[0][GRIDSIZE-1] = 0;
}
nAdj = 0;
if (g_aGrid[GRIDSIZE-1][0] == 1)
{
if (g_aGrid[GRIDSIZE-1][1] == 1)
nAdj++;
if (g_aGrid[GRIDSIZE-2][0] == 1)
nAdj++;
if (g_aGrid[GRIDSIZE-2][1] == 1)
nAdj++;
if (nAdj < 2)
g_aUpdate[GRIDSIZE-1][0] = 0;
}
nAdj = 0;
if (g_aGrid[GRIDSIZE-1][GRIDSIZE-1] == 1)
{
if (g_aUpdate[GRIDSIZE-1][GRIDSIZE-2] == 1)
nAdj++;
if (g_aUpdate[GRIDSIZE-2][GRIDSIZE-1] == 1)
nAdj++;
if (g_aUpdate[GRIDSIZE-2][GRIDSIZE-2] == 1)
nAdj++;
if (nAdj < 2)
g_aUpdate[GRIDSIZE-1][GRIDSIZE-1] = 0;
}
nAdj = 0;
}
windowproc.c:
#include "CGOL.h"
LRESULT CALLBACK WindowProc(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
switch (Msg)
{
HANDLE_MSG(hWnd, WM_CREATE, OnCreate);
HANDLE_MSG(hWnd, WM_CLOSE, OnClose);
HANDLE_MSG(hWnd, WM_DESTROY, OnDestroy);
default:
return DefWindowProcW(hWnd, Msg, wParam, lParam);
}
return 0;
}
wwinmain.c:
#include "CGOL.h"
CONST WCHAR g_wszClassName[] = L"CGOL_GovindParmar";
INT APIENTRY wWinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_z_ LPWSTR lpCmdLine,
_In_ INT nShowCmd
)
{
HWND hWnd;
MSG Msg;
if (RegisterWCEX(hInstance)==(ATOM)0)
{
MessageBoxW(NULL, L"Window registration failed", L"Error", MB_ICONSTOP | MB_OK);
return 0;
}
hWnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, g_wszClassName, L"Conway\'s Game of Life", WS_VISIBLE | WS_SYSMENU, 0, 0, GRIDSIZE * CELLSIZE, GRIDSIZE * CELLSIZE, NULL, NULL, hInstance, NULL);
if (hWnd == NULL)
{
MessageBoxW(NULL, L"Window creation failed", L"Error", MB_ICONSTOP | MB_OK);
return 0;
}
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
while (GetMessageW(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
return Msg.wParam;
}
My questions are:
You will notice in the code for
TimerProc
that I first process all the inner cells of the grid that are not an edge or corner of the grid, then the edges, then finally the corners. This is because I don't want undefined behavior when I access all the adjacent elements of a cell but there isn't anything adjacent in the array, like on an edge when there isn't anything above/below/left/right of the cell. Is there another way to do this so thatTimerProc
doesn't have the amount of code that it does?I use the array
g_aUpdate
to hold the next generation of cells then updateg_aGrid
as well as the drawing on the window based on its state every timer tick. Is there a way I can avoid the excessive copying and still produce the same output?Are there any GDI leaks in this code? I don't think there are.
General code review stuff, variable names, indentation, code style, etc.