康威生命遊戲 第一部分-基本功能實現(C++ & Windows SDK)

生命遊戲簡介

生命遊戲其實是一個零玩家遊戲,它包括一個二維矩形世界,這個世界中的每個方格居住着一個活着的或死了的細胞。一個細胞在下一個時刻生死取決於相鄰八個方格中活着的或死了的細胞的數量。如果相鄰方格活着的細胞數量過多,這個細胞會因爲資源匱乏而在下一個時刻死去;相反,如果周圍活細胞過少,這個細胞會因太孤單而死去。實際中,你可以設定周圍活細胞的數目怎樣時才適宜該細胞的生存。如果這個數目設定過低,世界中的大部分細胞會因爲找不到太多的活的鄰居而死去,直到整個世界都沒有生命;如果這個數目設定過高,世界中又會被生命充滿而沒有什麼變化。實際中,這個數目一般選取2或者3;這樣整個生命世界才不至於太過荒涼或擁擠,而是一種動態的平衡。這樣的話,遊戲的規則就是:當一個方格周圍有2或3個活細胞時,方格中的活細胞在下一個時刻繼續存活;即使這個時刻方格中沒有活細胞,在下一個時刻也會“誕生”活細胞。在這個遊戲中,還可以設定一些更加複雜的規則,例如當前方格的狀況不僅由父一代決定,而且還考慮祖父一代的情況。你還可以作爲這個世界的上帝,隨意設定某個方格細胞的死活,以觀察對世界的影響。
在遊戲的進行中,雜亂無序的細胞會逐漸演化出各種精緻、有形的結構;這些結構往往有很好的對稱性,而且每一代都在變化形狀。一些形狀已經鎖定,不會逐代變化。有時,一些已經成形的結構會因爲一些無序細胞的“入侵”而被破壞。但是形狀和秩序經常能從雜亂中產生出來。

(引用自百度百科)

程序界面

使用的知識點

  1. windows窗口的建立
  2. 按鈕
  3. 定時器
  4. C++基礎內存操作

具體實現

整個程序分爲兩部分:
1. 底層數據管理
2. 界面描繪和控制邏輯

1, 底層數據管理

爲了使邏輯結構可移植, 使用C++編寫了類CWorld, 不涉及任何界面繪圖操作

基本思路:

建立兩塊內存分別用於:
1. 記錄當前細胞分佈
2. 記錄下一回合細胞分佈

每一回合(Turn)的計算方法如下:
每個細胞的生死遵循下面的原則:
1. 如果一個細胞周圍有3個細胞爲生(一個細胞周圍共有8個細胞),則該細胞爲生(即該細胞若原先爲死,則轉爲生,若原先爲生,則保持不變) 。
2. 如果一個細胞周圍有2個細胞爲生,則該細胞的生死狀態保持不變;
3. 在其它情況下,該細胞爲死(即該細胞若原先爲生,則轉爲死,若原先爲死,則保持不變)

接口代碼(World.h):

#ifndef _LIFE_GAME_WORLD_H_
#define _LIFE_GAME_WORLD_H_

typedef struct _SCell
{
    int isAlive;
} 
SCell;
/* 注意:
    細胞的座標是從0開始的
*/
class CWorld
{
private:
    int m_width;
    int m_height;
    SCell* m_map1;  //地圖buffer1
    SCell* m_map2;  //地圖buffer2
    SCell* m_cur_map;   //當前地圖
    SCell* m_new_map;   //下一輪使用的地圖
    void setCurCell(int x, int y, int isAlive);
    void setNewCell(int x, int y, int isAlive);
    int getAroundCellNum(int x, int y); //獲得某個位置周圍存活的細胞數量
    int isPosValid(int x, int y);   //判定輸入位置是否有效, 1-有效 0-無效
    void swapMap(void) { SCell* temp = m_cur_map; m_cur_map = m_new_map; m_new_map = temp; }    //交換地圖
    SCell* getCell(SCell* buf, int x, int y) { return buf + y * m_width + x; }; //從地圖buffer中獲取某座標的細胞指針
public:
    CWorld(int width, int height);
    ~CWorld();
    void ramdomInit(void);  //隨機初始化地圖
    void killAll(void);     //殺死所有細胞
    void nextTurn(void);    //進入下一回合
    int getCellAlive(int x, int y); //獲取細胞存活狀態 , 返回值:1-存活, 0-死亡 -1-出錯
    int setCellAlive(int x, int y, int isAlive);    //設置細胞存活狀態 , 返回值:0-成功 負值-失敗
    int getWidth() { return m_width; }      //獲得當前地圖寬度
    int getHeight() { return m_height; }    //獲得當前地圖高度
};

#endif /* _LIFE_GAME_WORLD_H_ */

具體實現代碼:(World.cpp)

#include <time.h>
#include "World.h"
#include "global.h"


void CWorld::setCurCell(int x, int y, int isAlive)
{
    if (isPosValid(x, y) == 0)
    {
        return;
    }
    else
    {
        SCell* cell = getCell(m_cur_map, x, y);
        if (cell - m_cur_map >= m_width * m_height)
        {
            return;
        }
        cell->isAlive = isAlive;
    }
}

void CWorld::setNewCell(int x, int y, int isAlive)
{
    if (isPosValid(x, y) == 0)
    {
        return;
    }
    else
    {
        SCell* cell = getCell(m_new_map, x, y);
        if (cell - m_new_map >= m_width * m_height)
        {
            return;
        }
        cell->isAlive = isAlive;
    }
}

int CWorld::getAroundCellNum(int x, int y)
{
    int count = 0;

    if (isPosValid(x, y) == 0)
    {   //輸入不合法
        return -1;
    }
    //嘗試目標位置周圍的八個相鄰位置
    for (int i = x - 1; i <= x + 1; ++i)
    {
        for (int j = y - 1; j <= y + 1; ++j)
        {
            if (i == x && j == y)
            {
                continue;
            }
            if (isPosValid(i, j) == 1)
            {
                if (getCellAlive(i, j) == 1)
                {
                    count++;
                }
            }
        }
    }

    return count;
}

int CWorld::isPosValid(int x, int y)
{
    if (x >= m_width || x < 0 || y >= m_height || y < 0)
    {
        return 0;
    }
    return 1;
}

CWorld::CWorld(int width, int height)
{
    m_width = width;
    m_height = height;
    m_map1 = (SCell *) new SCell[m_width * m_height];
    m_map2 = (SCell *) new SCell[m_width * m_height];
    m_cur_map = m_map1;
    m_new_map = m_map2;
    killAll();
}


CWorld::~CWorld()
{
    delete[] m_map1;
    delete[] m_map2;
}

void CWorld::ramdomInit()
{
    killAll();

    srand((unsigned)time(NULL)); //用時間做種,每次產生隨機數不一樣

    for (int i = 0; i < m_width; ++i)
    {
        for (int j = 0; j < m_height; ++j)
        {
            int isAlive = rand() % 2;  //產生0或1的隨機數
            setCurCell(i, j, isAlive);
        }

    }

}

void CWorld::killAll(void)
{
    if (m_cur_map != NULL && m_new_map != NULL)
    {
        for (int i = 0; i < m_width; ++i)
        {
            for (int j = 0; j < m_height; ++j)
            {
                setCurCell(i, j, 0);
                setNewCell(i, j, 0);
            }
        }
    }

}

/*
每個細胞的生死遵循下面的原則:
1. 如果一個細胞周圍有3個細胞爲生(一個細胞周圍共有8個細胞),則該細胞爲生(即該細胞若原先爲死,則轉爲生,若原先爲生,則保持不變) 。
2. 如果一個細胞周圍有2個細胞爲生,則該細胞的生死狀態保持不變;
3. 在其它情況下,該細胞爲死(即該細胞若原先爲生,則轉爲死,若原先爲死,則保持不變)
*/
void CWorld::nextTurn(void)
{
    int aroundNum = 0;
    for (int i = 0; i < m_width; ++i)
    {
        for (int j = 0; j < m_height; ++j)
        {
            aroundNum = getAroundCellNum(i, j);
            if (aroundNum == 2)
            {
                setNewCell(i, j, getCellAlive(i, j));
            }
            else if (aroundNum == 3)
            {
                setNewCell(i, j, 1);
            }
            else
            {
                setNewCell(i, j, 0);
            }
        }
    }
    swapMap();
}

int CWorld::getCellAlive(int x, int y)
{
    if (isPosValid(x, y) == 0)
    {
        return -1;
    }
    SCell* cell = getCell(m_cur_map, x, y);
    return cell->isAlive;
}

int CWorld::setCellAlive(int x, int y, int isAlive)
{
    if (isPosValid(x, y) == 0)
    {
        return -1;
    }
    if (isAlive != 0 && isAlive != 1)
    {
        return -2;
    }
    SCell* cell = getCell(m_cur_map, x, y);
    cell->isAlive = isAlive;
    return 0;
}

2, 界面描繪和控制邏輯

main.cpp代碼如下:

#include <time.h>
#include "global.h"
#include "World.h"

#define WORLD_TIMER_ID (1)  //定時器ID
#define WORLD_TIMER_ELAPSE (1000)   //定時器超時時間

#define RANDOM_BTN_ID (1)
#define START_BTN_ID (2)
#define PAUSE_BTN_ID (3)
#define NEXT_BTN_ID (4)
#define KILL_ALL_BTN_ID (5)

class CWorld *g_world = NULL;   //全局世界指針

//定時器超時回調函數
void CALLBACK WorldTimerCallBack(HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime);
//描畫函數
void CleanWorld(HDC hdc);
void DrawWorld(CWorld* world, int world_w, int world_h, HDC hdc);
void DrawCell(CWorld* world, HDC hdc);
void DrawGrid(HDC hdc, int w, int h);

void CreateButton(HWND hwnd, HINSTANCE hInstance);

/* 全局變量定義區 */
char* g_szApplicationName = "LifeGame";
char* g_szWindowClassName = "This Is My Window Class";

/* 消息回調函數 */
LRESULT CALLBACK WindowProc(HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam)
{
    //存儲用戶窗口的寬和高
    static int cxClient, cyClient;
    //界面字體寬高
    static int cxChar, cyChar;

    //用於創建後備緩衝
    static HDC      hdcBackBuffer;
    static HBITMAP  hBitmap;
    static HBITMAP  hOldBitmap;

    switch (msg)
    {
    case WM_CREATE:
        {
        RECT rect;

        GetClientRect(hwnd, &rect);

        cxClient = rect.right;
        cyClient = rect.bottom;

        //將窗口移動到屏幕中央
        int scrWidth, scrHeight;
        scrWidth = GetSystemMetrics(SM_CXSCREEN);
        scrHeight = GetSystemMetrics(SM_CYSCREEN);
        GetWindowRect(hwnd, &rect);
        MoveWindow(hwnd, (scrWidth - rect.right) / 2, (scrHeight - rect.bottom) / 2, rect.right - rect.left, rect.bottom - rect.top, FALSE);

        //創建世界和生物
        g_world = new CWorld(WORLD_WIDTH, WORLD_HEIGHT);
        DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
        CreateButton(hwnd, ((LPCREATESTRUCT)lParam)->hInstance);
        EnableWindow(GetDlgItem(hwnd, START_BTN_ID), TRUE); //啓用開始按鈕
        EnableWindow(GetDlgItem(hwnd, PAUSE_BTN_ID), FALSE);    //禁用暫停按鈕
        EnableWindow(GetDlgItem(hwnd, NEXT_BTN_ID), TRUE);  //啓用下一步按鈕
        EnableWindow(GetDlgItem(hwnd, KILL_ALL_BTN_ID), TRUE);  //啓用殺死所有按鈕

        //後備緩衝區相關處理
        hdcBackBuffer = CreateCompatibleDC(NULL);
        HDC hdc = GetDC(hwnd);
        hBitmap = CreateCompatibleBitmap(hdc, cxClient, cyClient);
        hOldBitmap = (HBITMAP)SelectObject(hdcBackBuffer, hBitmap);
        //銷燬處理
        ReleaseDC(hwnd, hdc);

        }
        break;
    case WM_COMMAND:    //按鈕被按下後的響應
    {
        int button_id = LOWORD(wParam);
        switch (button_id)
        {
        case RANDOM_BTN_ID: //隨機初始化按鈕按下
            g_world->ramdomInit();
            DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
            break;
        case START_BTN_ID:  //開始按鈕按下
            SetTimer(hwnd, WORLD_TIMER_ID, WORLD_TIMER_ELAPSE, WorldTimerCallBack); //啓動計時器
            EnableWindow(GetDlgItem(hwnd, RANDOM_BTN_ID), FALSE);   //禁用隨機生成按鈕
            EnableWindow(GetDlgItem(hwnd, START_BTN_ID), FALSE);    //禁用開始按鈕
            EnableWindow(GetDlgItem(hwnd, PAUSE_BTN_ID), TRUE); //啓用暫停按鈕
            EnableWindow(GetDlgItem(hwnd, NEXT_BTN_ID), FALSE); //禁用下一步按鈕
            EnableWindow(GetDlgItem(hwnd, KILL_ALL_BTN_ID), FALSE); //禁用殺死所有按鈕
            break;
        case PAUSE_BTN_ID:  //暫停按鈕按下
            KillTimer(hwnd, WORLD_TIMER_ID);    //銷燬計時器
            EnableWindow(GetDlgItem(hwnd, RANDOM_BTN_ID), TRUE);    //啓用隨機生成按鈕
            EnableWindow(GetDlgItem(hwnd, START_BTN_ID), TRUE); //啓用開始按鈕
            EnableWindow(GetDlgItem(hwnd, PAUSE_BTN_ID), FALSE);    //禁用暫停按鈕
            EnableWindow(GetDlgItem(hwnd, NEXT_BTN_ID), TRUE);  //啓用下一步按鈕
            EnableWindow(GetDlgItem(hwnd, KILL_ALL_BTN_ID), TRUE);  //啓用殺死所有按鈕
            break;
        case NEXT_BTN_ID:   //下一步按鈕按下
            g_world->nextTurn();
            DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
            break;
        case KILL_ALL_BTN_ID:   //殺死所有細胞按鈕按下
            g_world->killAll();
            DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
            break;
        default:
            break;
        }
    }
        break;
    case WM_KEYUP:
        //按下Esc退出
        switch (wParam)
        {
        case VK_ESCAPE:
            PostQuitMessage(0);
            break;
        }
        break;
    case WM_PAINT:
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);
        //將後備緩衝區塗上黑色背景
        BitBlt(hdcBackBuffer,
                0,
                0,
                cxClient,
                cyClient,
                NULL,
                NULL,
                NULL,
                BLACKNESS);

        //描畫世界
        DrawGrid(hdcBackBuffer, WORLD_WIDTH, WORLD_HEIGHT);
        DrawCell(g_world, hdcBackBuffer);

        BitBlt(ps.hdc, 0, 0, cxClient, cyClient, hdcBackBuffer, 0, 0, SRCCOPY);
        EndPaint(hwnd, &ps);
        break;
    case WM_SIZE:
    {
        //變更窗口大小時的處理
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        //改變世界的大小
        //world->setWorldSize(cxClient, cyClient);

        SelectObject(hdcBackBuffer, hOldBitmap);
        DeleteObject(hBitmap);
        HDC hdc = GetDC(hwnd);
        hBitmap = CreateCompatibleBitmap(hdc,
            cxClient,
            cyClient);

        ReleaseDC(hwnd, hdc);
        SelectObject(hdcBackBuffer, hBitmap);
    }
    break;
    case WM_DESTROY:
        //銷燬世界
        delete g_world;

        //清除並銷燬後備緩衝區
        SelectObject(hdcBackBuffer, hOldBitmap);
        DeleteDC(hdcBackBuffer);
        DeleteObject(hBitmap);

        //終了程序,發送WM_QUIT消息  
        PostQuitMessage(0);
        break;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     szCmdLine,
    int       iCmdShow)
{
    HWND hWnd;  //窗口句柄
    WNDCLASSEX winclass;    //窗口類對象

                            //窗口類對象的初始化
    winclass.cbSize = sizeof(WNDCLASSEX);
    winclass.style = CS_HREDRAW | CS_VREDRAW;
    winclass.lpfnWndProc = WindowProc;
    winclass.cbClsExtra = 0;
    winclass.cbWndExtra = 0;
    winclass.hInstance = hInstance;
    winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    winclass.hbrBackground = NULL;
    winclass.lpszMenuName = NULL;
    winclass.lpszClassName = g_szWindowClassName;
    winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    //註冊窗口類
    if (!RegisterClassEx(&winclass))
    {
        MessageBox(NULL, "Registration Failed!", "Error", 0);
        return 0;
    }

    //創建窗口  
    hWnd = CreateWindowEx(NULL,                 // extended style
        g_szWindowClassName,  // window class name
        g_szApplicationName,  // window caption
        WS_OVERLAPPEDWINDOW,  // window style
        0,                    // initial x position
        0,                    // initial y position
        WINDOW_WIDTH,         // initial x size
        WINDOW_HEIGHT,        // initial y size
        NULL,                 // parent window handle
        NULL,                 // window menu handle
        hInstance,            // program instance handle
        NULL);                // creation parameters

                              //容錯處理
    if (!hWnd)
    {
        MessageBox(NULL, "CreateWindowEx Failed!", "Error!", 0);
        return 0;
    }

    //顯示窗口
    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

//超時後回調函數
void CALLBACK WorldTimerCallBack(HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{
    g_world->nextTurn();
    DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
}

//描畫整個世界
void DrawWorld(CWorld * world, int world_w, int world_h, HDC hdc)
{
    CleanWorld(hdc);
    DrawGrid(hdc, world_w, world_h);
    DrawCell(world, hdc);
}

//將世界塗成黑色(背景色)
void CleanWorld(HDC hdc)
{
    HPEN BlackPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
    HBRUSH BlackBrush = CreateSolidBrush(RGB(0, 0, 0));
    SelectObject(hdc, BlackPen);
    SelectObject(hdc, BlackBrush);
    Rectangle(hdc, 0, 0, WORLD_WIDTH * CELL_SIZE, WORLD_HEIGHT * CELL_SIZE);
    DeleteObject(BlackPen);
    DeleteObject(BlackBrush);
}

//描畫所有細胞
void DrawCell(CWorld* world, HDC hdc)
{
    HPEN BluePen = CreatePen(PS_SOLID, 1, RGB(0, 0, 128));
    HBRUSH BlueBrush = CreateSolidBrush(RGB(0, 0,128));
    SelectObject(hdc, BluePen);
    SelectObject(hdc, BlueBrush);
    for (int i = 0; i < world->getWidth(); ++i)
    {
        for (int j = 0; j < world->getHeight(); ++j)
        {
            if (world->getCellAlive(i, j) == 1)
            {
                Rectangle(hdc, i * CELL_SIZE, j * CELL_SIZE, i * CELL_SIZE + CELL_SIZE, j * CELL_SIZE + CELL_SIZE);
            }
        }
    }

    DeleteObject(BluePen);
    DeleteObject(BlueBrush);
}

//描畫網格
void DrawGrid(HDC hdc, int w, int h)
{
    HPEN GrayPen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128));
    //HBRUSH GreenBrush = CreateSolidBrush(RGB(0, 255, 0));
    SelectObject(hdc, GrayPen);
    //SelectObject(hdc, GreenBrush);
    for (int i = 0; i <= w; ++i)
    {
        MoveToEx(hdc, i * CELL_SIZE, 0, NULL);
        LineTo(hdc, i * CELL_SIZE, h * CELL_SIZE);
    }
    for (int i = 0; i <= h; ++i)
    {
        MoveToEx(hdc, 0, i * CELL_SIZE, NULL);
        LineTo(hdc, w * CELL_SIZE, i * CELL_SIZE);
    }

    DeleteObject(GrayPen);
    //DeleteObject(GreenBrush);
}

void CreateButton(HWND hwnd, HINSTANCE hInstance)
{
    RECT rect;
    GetClientRect(hwnd, &rect);
    int cxClient = rect.right;

    /* 建立按鈕 */
    int cxChar = LOWORD(GetDialogBaseUnits());
    int cyChar = HIWORD(GetDialogBaseUnits());
    //開始按鈕
    int button_w = cxChar * 12;
    int button_h = cyChar * 2;
    int button_x = cxClient - cxChar * 15;
    int random_btn_y = cyChar * 1;
    int start_btn_y = random_btn_y + button_h + cyChar;
    int pause_btn_y = start_btn_y + button_h + cyChar;
    int next_btn_y = pause_btn_y + button_h + cyChar;
    int kill_all_btn_y = next_btn_y + button_h + cyChar;

    CreateWindow(TEXT("Button"), TEXT("隨機初始化"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        button_x, random_btn_y, button_w, button_h,
        hwnd, (HMENU)RANDOM_BTN_ID,
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("開始"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        button_x, start_btn_y, button_w, button_h,
        hwnd, (HMENU)START_BTN_ID,
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("暫停"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        button_x, pause_btn_y, button_w, button_h,
        hwnd, (HMENU)PAUSE_BTN_ID,
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("下一步"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        button_x, next_btn_y, button_w, button_h,
        hwnd, (HMENU)NEXT_BTN_ID,
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("殺死所有"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        button_x, kill_all_btn_y, button_w, button_h,
        hwnd, (HMENU)KILL_ALL_BTN_ID,
        hInstance, NULL);
}

最後, 附上global.h代碼

#pragma once

#include <windows.h>
#include <iostream>

#define WINDOW_WIDTH  800
#define WINDOW_HEIGHT 600
#define WORLD_WIDTH 30
#define WORLD_HEIGHT 20
#define CELL_SIZE 20

程序還有一些地方需要改進, 將在下次進行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章