直接使用Win32 API绘图实现基本的细胞自动机(生命游戏)演示

直接使用Win32 API绘图实现基本的细胞自动机(生命游戏)演示

由于编写程序的时候没有意识到直接在设备上下文中绘图的性能低下问题,本程序会占用一个CPU核心的100%的资源。如果改成采用在内存上下文环境绘制然后复制到设备上下文的做法资源消耗会好一点。

//本程序使用 Visual Studio 2015 生成的 Win32 窗口程序模板 开发
//使用 Win32 API 绘图
//实现基本的细胞自动机演示
//
//目前已知问题:
//存在内存泄漏,但具体哪里泄漏还未找到
//长时间运行会卡死崩溃,怀疑与刷新频率较高带来的高系统资源占用有关
//
// LifeCompute2.cpp : 定义应用程序的入口点。
//细胞自动机:
//一个活的细胞周围的八个格子中
//如果有2或者3个格子是活的,则继续存活
//否则死亡
//一个死亡的格子周围的八个格子中
//如果有3个格子是活的则变成活的
//否则依然是死亡

//要使用两个二维数组来代表前后两轮的生存状况
//不可以只使用一个二维数组并在其中一个个的更新细胞状态
//因为相邻的细胞之间会相互影响

#include "stdafx.h"
#include "LifeCompute2.h"
#include "resource.h"        //资源文件中添加了一个draw菜单,用以开始。菜单的ID为 IDM_DRAW
#include<math.h>

#define MAX_LOADSTRING 100

//程序配置:提供统一的绘图相关的设置和格子数量设置
typedef struct setting
{
    COLORREF activeColor;                        //存活状态的颜色
    COLORREF inactiveColor;                        //死亡状态的颜色
    COLORREF backgroundColor;                    //背景颜色,实际上用于绘制网格线
    int lineWidth;                                //网格线宽度,单位:像素

    int countX;                                    //横向格子数量,即列数
    int countY;                                    //纵向格子数量,即行数

    int a;                                        //正方形格子边长,单位:像素
}SETTING;

// 全局变量,VC自动生成: 
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名
//全局变量
SETTING setting;                                //全局设置结构体对象
bool **ground=NULL;                                //保存当前格子信息的二维数组指针

// 此代码模块中包含的函数的前向声明,VC自动生成: 
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
// 此代码模块中包含的函数的前向声明
void draw(HWND);                                //开始
void updateGround(HWND);                        //更新

//VC自动生成入口
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 在此放置代码。

    // 初始化全局字符串
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_LIFECOMPUTE2, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 执行应用程序初始化: 
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_LIFECOMPUTE2));

    MSG msg;

    // 主消息循环: 
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  函数: MyRegisterClass()
//
//  目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_LIFECOMPUTE2));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_LIFECOMPUTE2);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   函数: InitInstance(HINSTANCE, int)
//
//   目的: 保存实例句柄并创建主窗口
//
//   注释: 
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 将实例句柄存储在全局变量中

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 分析菜单选择: 
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            case IDM_DRAW:                                //点击了draw菜单,开始运算并绘制
                draw(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_TIMER:
        updateGround(hWnd);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

void drawBackgroundBlocks(HWND win) {
    HDC dc=GetDC(win);
    HPEN pen=CreatePen(PS_SOLID, setting.lineWidth, setting.backgroundColor);
    SelectObject(dc, pen);
    for (int i = 1; i <= setting.countX+1; i++) {
        MoveToEx(dc,(i-1)*(setting.a+setting.lineWidth)+setting.lineWidth/2 ,0, NULL);
        LineTo(dc, (i - 1)*(setting.a + setting.lineWidth) + setting.lineWidth / 2, setting.countY*(setting.a + setting.lineWidth));
    }

    for (int i = 1; i <= setting.countY + 1; i++) {
        MoveToEx(dc,  0, (i - 1)*(setting.a + setting.lineWidth) + setting.lineWidth / 2, NULL);
        LineTo(dc, setting.countX*(setting.a + setting.lineWidth), (i - 1)*(setting.a + setting.lineWidth) + setting.lineWidth / 2);
    }

    DeleteObject( pen);
}

//初始化设置。注意:格子数量过多可能导致系统资源占用较高
void initSetting() {
    setting.activeColor = RGB(0, 192, 0);
    setting.inactiveColor = RGB(0, 64, 0);
    setting.backgroundColor = RGB(32,32,32);
    setting.lineWidth = 1;

    setting.countX = 120;
    setting.countY = 70;

    setting.a = 10;
}

//初始化格子,分配内存空间
void initGround() {
    ground = new bool*[setting.countX];
    for (int i = 1; i <= setting.countX; i++) {
        ground[i-1] = new bool[setting.countY];
        for (int j = 1; j <= setting.countY; j++) {
            ground[i-1][j-1] = false;
        }
    }
}

//释放动态分配的二维数组空间
void cleanGround() {
    for (int i = 0; i < setting.countX; i++)
    {
        delete []ground[i];
    }
    delete []ground;
}

//设置第一轮的生存状况
void setBorn() {
    if (ground == NULL) {
        return;
    }

    const int n = 75;                                                    //控制随机生成的信息的数量
    int range = 22;                                                        //初始信息的范围

    int x[n];                                                            //随机点的横坐标
    int y[n];                                                            //随机点的纵坐标

    for (int i = 0; i < n; i++)    
    {
        x[i] = rand() % range + setting.countX/2-range/2;                //生成随机点
        y[i]= rand() % range + setting.countY/2-range/2;
        ground[x[i]][y[i]] = true;                                        //将这些随机点设置为活的
    }
}

//绘制坐标为x,y的方形
void drawOneRect(HDC dc,int x,int y,bool active) {
    HPEN pen;
    HBRUSH brush;
    if (active) {
         pen = CreatePen(PS_SOLID, 0, setting.activeColor);
         brush = CreateSolidBrush(setting.activeColor);
    }
    else {
         pen = CreatePen(PS_SOLID, 0, setting.inactiveColor);
         brush = CreateSolidBrush(setting.inactiveColor);
    }

    SelectObject(dc, pen);
    SelectObject(dc, brush);

    int left = (x-1) * setting.a + x * setting.lineWidth;
    int top = (y-1) * setting.a + y * setting.lineWidth;
    int right = left + setting.a;
    int bottom = top + setting.a;

    Rectangle(dc, left, top, right, bottom);

    DeleteObject(brush);
    DeleteObject(pen);    
}

//将二维数组中的生存状况数据绘制到客户区
void drawGroundStatus(HWND win) {
    HDC dc=GetDC(win);
    HDC memDC = CreateCompatibleDC(dc);
    RECT rect;
    rect.top = 0;
    rect.left = 0;
    rect.right = setting.a*setting.countX + setting.lineWidth*(setting.countX + 1);
    rect.bottom = setting.a*setting.countY + setting.lineWidth*(setting.countY + 1);

    HBITMAP map = CreateCompatibleBitmap(dc,rect.right,rect.bottom);
    HBITMAP oldMap=(HBITMAP) SelectObject(memDC, map);

    for (int i = 1; i <= setting.countX; i++)
    {
        for (int j = 1; j <= setting.countY; j++) {
            drawOneRect(memDC, i, j, ground[i - 1][j - 1]);
        }
    }

    BitBlt(dc, rect.left, rect.top, rect.right, rect.bottom, memDC, rect.left, rect.top, SRCCOPY);

    SelectObject(memDC, oldMap);
    
    DeleteObject(map);

    ReleaseDC(win, memDC);
    ReleaseDC(win, dc);        
}

//计算坐标为x,y的格子周围的活的格子数量
//如果该格子靠近边缘,则周围格子数量少于8个
int countAround(int xa,int ya) {
    //1~30

    int x = xa - 1;
    int y = ya - 1;
    int count = 0;
    ////////////////////////////
    if (x - 1 > 0 && y - 1 > 0) {                        //左上
        if (ground[x - 1][y - 1]) {
            count++;
        }
    }

    if ( y - 1 > 0) {                                    //上
        if (ground[x][y - 1]) {
            count++;
        }
    }

    if (x + 1 <setting.countX && y - 1 > 0) {            //右上
        if (ground[x + 1][y - 1]) {
            count++;
        }
    }
    //////////////////////////
    if (x - 1 > 0) {                                    //左边
        if (ground[x - 1][y ]) {
            count++;
        }
    }
    if (x + 1<setting.countX ) {                        //右边
        if (ground[x + 1][y ]) {
            count++;
        }
    }
    //////////////////////////
    if (x - 1 > 0 && y + 1 <setting.countY) {            //左下
        if (ground[x - 1][y + 1]) {
            count++;
        }
    }
    if (y+1<setting.countY ) {                            //下方
        if (ground[x][y+1 ]) {
            count++;
        }
    }
    if (x + 1 <setting.countX && y + 1<setting.countY) {        //右下
        if (ground[x + 1][y+ 1]) {
            count++;
        }
    }
    
    return count;
}

//判断坐标为x,y的格子下一轮的生存状态
bool sentence(int x, int y) {
    int count = countAround( x, y);
    if (ground[x - 1][y - 1]) {                                    //如果当前是活的
        if (count == 2 || count == 3) {                            //周围有2或3个活的
            return true;                                        //下一轮继续活着
        }
        else {                                                    //周围活着的数量不是2或3
            return false;                                        //下一轮死亡
        }
    }
    else {                                                        //如果当前是死的
        if (count == 3) {                                        //周围或者的数目是3
            return true;                                        //下一轮是活的
        }
        else {                                                    //周围或者的数目不是3
            return false;                                        //下一轮死亡
        }
    }
    
}

//更新一轮生存数据
void getNextGen() {
    bool **ret;                                                    //新一轮的数据位置
    ret = new bool*[setting.countX];

    for (int i = 0; i < setting.countX; i++) {                    //分配空间
        ret[i] = new bool[setting.countY];
        for (int j = 0; j < setting.countY; j++) {
            ret[i][j] = sentence(i + 1, j + 1);                    //根据当前状况填入下一轮每个格子的状况
        }
    }
    cleanGround();                                                //清理旧的内存空间
    ground = ret;                                                //将新的数据交给指针
}

//控制更新数据和绘图
void updateGround(HWND win) {
    drawBackgroundBlocks(win);                        //绘制格子
    drawGroundStatus(win);                            //绘制生存状态
    getNextGen();                                    //更新数据
}

//控制运算与绘图
void draw(HWND win) {
    initSetting();                                    //初始化设置
    drawBackgroundBlocks(win);                        //绘制背景网格
    initGround();                                    //初始化网格数据
    setBorn();                                        //设置第一轮生存状况
    drawGroundStatus(win);                            //绘制生存状况

    SetTimer(win, ID_TIMER, 100, NULL);                //使用计时器消息触发更新迭代

}