本文從分析Windows掃雷游戲的功能特點開始,應用面向對象的可視化編程語言Visual C++給出了個功能模塊的具體實現方法,並提供了編寫小游戲程序的一般方法和Visual C++的一些使用技巧。
首先分析掃雷的最基本功能。
點擊鼠標左鍵於未知區域,如果未知區域有雷,游戲停止,顯示所有的地雷。如果沒雷,則顯示周圍雷數,如果周圍沒雷,則再查看周圍八個區域是否有雷直到有雷為止並顯示,這其實是一個遞歸過程。
點擊鼠標右鍵於未知區域,則將其置為有雷而不管是否真的有雷。可選擇初、中、高三級並可自定義雷數和區域大小。
雷區上部左側顯示總雷數減被標明有雷區域的數目。
雷區上部中間位置顯示一按鈕用於開局和顯示鼠標動作的結果。
雷區上部右側顯示掃雷的時間。
將雷全部掃清後,則顯示一對話框將你的姓名記入排行榜。以時間排序。
為完成上述功能,應用Visual C++的具體技術細節如下:
1. 應用AppW
izard創建基於SDI的應用程序CBombApp,去除打印和狀態條支持,在資源編輯器中修改菜單和相應的加速鍵,使其與Windows掃雷游戲一致。具體為開局(ID-GAME-BEGIN)、初級(ID-GAME-JUNIOR)、中級(ID-GAME-MIDDLE)、高級(ID-GAME-SENIOR)、自定義(ID-GAME-CUSTOM)、顏色(ID-GAME-COLOR)、英雄榜(ID-GAME-SORT)、退出(ID-GAME-EXIT)、幫助(ID-HELP)。
2. 在資源編輯器中對應於雷區的每個小區域的13個屬性。用畫筆或其他繪圖工具繪制出相對應的13個10乘10的16色小位圖,三個對應於小人表情的20乘20的16色小位圖,供更換顏色時使用的一套與前16個對應的單色位圖,顯示時間和雷數的0~9十個數字位圖(底色為黑色)。
定制CUSTOMER對話框,內含三個靜態文本控件和三個編輯控件,三個編輯控件分別對應成員m_iRowNum,m_iColumnNum,m_iBombNum。該對話框用於定制雷數,行列數,其相應的MFC類為CCustomer。定制SORT對話框,內含九個靜態文本控件,其中六個顯示排行榜的姓名和時間,其對應的MFC類為CSort。定制INPUT對話框,內含一個靜態文本控件和一個編輯控件,編輯控件用於在游戲成功結束時輸入姓名,其對應的MFC類為CInput。
3. 定義類Bomb,封裝每顆雷的相關屬性。
Class Bomb
{
public:
int isbomb;//決定初始時是否是雷
bool issel; //判斷區域是否被處理過且周圍有雷
bool isdone;//判斷遞歸時是否被處理過
int num; //周圍雷數
bool findbomb; //排雷者認為是雷時置一(但是不一定真是雷)
} ;
4. 重載CMainFrame中PreCreateWindow,並設置相應屬性,使其窗體大小固定,這樣就固定了顯示區域的大小為初始10乘10個雷和外加雷區上部的控制區域,部分代碼如下。
cs.style=WS_OVERLAPPED|WS_SYSMENU| WS_BORDER|WS_MINIMIZEBOX;
cs.cy = 10*15+6;
cs.cx = 10*15+60; //6和60分別是橫縱的附加值,用於邊框、菜單、標題條、控制區域。
5. 游戲的主要工作就是呈現不斷變換的圖形或動畫,並按用戶的輸入交互進行顯示,而Windows文檔-----視窗構架中的視窗的功能正是接受用戶輸入並負責顯示,因此由CView類來完成掃雷的大部分工作。在CBombVIEw中定義下列成員變量記錄相關操作的結果或對象的狀態。
Bomb m_bomb[30][30]; //最大的地雷區域
CString m_CurrentTime; //用於記錄並顯示掃雷時間
CTime m_BeginTime;//記錄游戲開始時的時間
BOOL m_TimerBegin;//定時器是否開啟
int m_iBomNum;//雷的數目
int m_iRow;//雷的行數
int m_iColumn;//雷的列數
int m_iBombFound;//指示被認為是雷的數目
CBitmapButton m_bitButton;//控制區的位圖按鈕
int m_CurrentLevel;//指示當前游戲的級別
BOOL m_bIsColor;//指示當前是彩色還是單色
CBitmap m_bmBomb[12];//用於存放12個小位圖
int m_iGameOver;//游戲未結束置0,已清除所有的雷置1,被炸死置2。
重載CBombVIEw中OnCreate函數創建位圖按鈕,該位圖按鈕的兩幅位圖對應了正常、排雷正確兩種狀態,當要顯示被炸死的狀態時應動態銷毀該按鈕,並重新創建一位圖對應正常和被炸死兩種狀態,將該位圖按鈕的ID號定為ID_GAME_BEGIN,這樣一來當點擊按鈕時便可重新開始游戲,部分代碼如下。
CRect rcclIEnt;
GetClientRect(&rcclIEnt);
CRect rect(rcclient.cx/2-8,10,rcclIEnt.cx/2+8,20);
m_Button.Create("New",BS_DEFPUSHBUTTON|WS_VISIBLE|
BS_OWNERDRAW,rect,this,ID_GAME_BEGIN);
m_Button.LoadBitmaps(IDB_FACE1, IDB_FACE2);
顯示時間的功能相對比較簡單,在響應第一個WM_LBUTTOMDOWN消息時開啟定時器,並記錄游戲開始的時間,在WM_TIME消息響應函數OnTimer中獲得當前時間,減去游戲開始時的時間,在顯示時間的客戶區域顯示得到的時間差(用數字位圖),當游戲結束時(排完全部雷或被炸死)關閉定時器,停止顯示。
WM_LBUTTOMDOWN消息響應函數OnLButtomDown是處理用戶輸入的主要執行者,函數首先判斷點中位置是否是雷,是則關閉定時器,銷毀原位圖按鈕,創建一對應正常和被炸死兩種狀態的新位圖按鈕,並調用SetState將其設置為PUSHDOWN(小人哭的狀態),將m_bGameOver,置為TRUE標志游戲結束,否則先調用SetState 設置位圖按鈕為PUSHDOWN (小人笑的狀態),並在OnLButtomUp中設置位圖按鈕為正常狀態,然後調用Caculate函數記下周圍雷的數目,最後調用Invalidate使客戶區無效,迫使OnDraw函數重繪客戶區域,在調用Invalidate時不應重畫背景,避免閃爍,這樣就完成了在雷區按下左鍵的響應動作。
WM_RBUTTOMDOWN消息響應函數OnLButtomDown將被認為有雷位置的m_iBombNum.findbomb置一,減少左上角的雷記數,然後判斷是否真正全部排完了雷,是則結束游戲彈出INPUT對話框,讓掃雷的人輸入姓名,在響應IDOK通知碼時將其寫入注冊表,沒有全部排完則使客戶區無效,迫使OnDraw函數重繪客戶區域完成在雷區按下右鍵的動作。
OnDraw函數在每次點擊左鍵或右鍵時都會被調用重雷區和控制區域,因為點擊情況的復雜性和雷屬性的多元化導致OnDraw函數需要精心設計。
函數Caculate計算某個雷周圍的雷數,根據前面的分析知道,計算某個雷周圍的雷數本身就是一個遞歸過程,在編制時應注意遞歸的邊界條件,稍不注意會陷入無窮遞歸而耗盡了系統的資源。
6. 菜單命令的響應是游戲交互的另一個重要方式,下面的九個命令響應函數分別與九個菜單項相對應,用以完成用戶的更新和設置命令。
OnGameBegin完成初始時間清零,隨機布雷,依據顏色指示裝載12幅小位圖,使雷區無效調用OnDraw重繪等工作。其中隨機布雷就是多次調用rand(),根據其返回值決定m_bomb[I][j].isbomb的值。
OnGameCustom首先彈出CCustomer對話框,在用戶輸入設置後響應IDOK通知碼時將用戶輸入的雷數、行列數分別賦給CVIEw的數據成員m_iBombNum、m_iRow、m_iColumn,得到框架窗口的指針,用其調用MoveWindow將窗口調至所需大小,銷毀原位置的位圖按鈕,並在X軸坐標為新窗口寬1/2減8處,Y軸坐標為新窗口頂部加30的位置創建一新按鈕。最後調用OnGameBegin重新開始游戲。
OnGameJunior、OnGameMiddle、OnGameSenior三個函數與OnGameCustom類似,只不過將分別賦給CVIEw的數據成員m_iBombNum、m_iRow、m_iColumn以固定的值,其大小可由編程者自定,筆者定為Junior(20,8,8,)、Middle(40,13,13)、Senior(99,20,25)。
OnGameColor函數銷毀原位圖按鈕,根據重新裝載位圖的標志m_IsColor來創建新的位圖按鈕,將裝載12幅單色位圖的標志取反,調用OnGameBegin重新開始游戲。
OnGameSort函數根據當前游戲級別從注冊表中讀出排名並彈出SORT對話框顯示結果。到現在為止,一個自己編制的掃雷游戲就基本完成了,將數百行代碼編譯一下,找出小錯誤,最後BUILD一遍,RUN一下,好了,可愛的掃雷游戲就出現在你的面前了。怎麼樣,自己的勞動成果並不比Microsoft的差吧,而且你還可以把小位圖畫成各種樣子,當然你自己要認得出才行了。