歡樂連連看小遊戲製作
之前完成了歡樂連連看的實驗,現在來做一下總結,以實驗的步驟為綱進行。
一.實驗目的和要求
1. 目的
通過連連看專案,達到如下目標:
(1)瞭解業務背景,調研與連連看同類型遊戲,瞭解連連看遊戲的功能和規則等。
(2)掌握C++開發工具和整合開發環境(Microsoft Visual Studio 2015)
(3)掌握C++面向物件的程式設計思想和C++的基礎程式設計。
(4)瞭解MFC基本框架,包括MFC Dialog應用程式和GDI程式設計。
(5)瞭解線性結構,重點掌握陣列和棧操作,陣列遍歷、消子和勝負判斷等演算法。
(6)瞭解專案開發流程,瞭解系統需求分析和設計,應用迭代開發進行專案開發。
(7)養成良好的編碼習慣和培養軟體工程化思維,綜合應用“C++程式設計、MFC Diaolog、演算法、線性結構”等知識,開發“連連看遊戲”桌面應用程式,達到掌握和應用線性結構核心知識的目的。
2. 要求
實現基本功能:開始遊戲、暫停遊戲、消子、判斷勝負、提示、重排、計時等。
(1)主介面:設計“歡樂連連看”專案的主介面,在主介面上新增一個背景圖片,並在適當的地方新增“基本模式”、“休閒模式”、“關卡模式”、“幫助”、“設定”、“排行榜”按鈕。
(2)開始遊戲:當玩家在主介面選擇“基本模式”時,出現基本遊戲介面,並隱藏主介面,玩家點選“開始遊戲”按鈕,生成遊戲地圖。
(3)消子:對玩家選中的兩張圖片進行判斷,判斷是否符合消除規則。符合一條直線連通、兩條直線連通、三條直線連通這三種情況之一就可以消除。如果可以消除,從遊戲地圖中提示連線路徑,然後消除這兩張圖片。如果不能消除,則保持原來的遊戲地圖。
消子規則
(4)判斷勝負:在基本模式下如果將遊戲地圖中的所有的圖片都消除,則提示玩家獲勝,並且可以重新開始新遊戲。
(5)提示:可以提示介面上能夠消除的一對圖片。
(6)重排:根據隨機數,重新排列遊戲地圖上的圖片。
(7)計時:設定一定的時間來輔助遊戲是否結束。
(8)暫停遊戲:遊戲過程中可以暫停計時,並且將遊戲地圖遮蓋,按鈕顯示為繼續遊戲。選擇繼續遊戲,計時繼續。
二.分析與設計
歡樂連連看專案採用MFC框架,軟體採用三層結構。使用二維陣列來儲存遊戲地圖中的資料,基本實現了連連看的核心功能。
1. 資料結構設計
//儲存遊戲地圖中的一個點的資訊
typedef struct tagVertex
{
int row; //行
int col; //列
int disa; //資訊類
}Vertex;
核心類設計
- CGameLogic類
資料成員:
static int s_nRows; //遊戲行數
static int s_nCols; //遊戲列數
static int s_nPicNum; //圖片數
int PicNum;
Vertex m_avPath[4]; //儲存在進行連線判斷時所經過的頂點
int m_nVexNum; //頂點數
成員函式:
int **InitMap();
void ReleaseMap(int ** &pGameMap);
bool IsLink(int ** pGameMap, Vertex V1, Vertex V2); //判斷是否連通
void Clear(int ** pGameMap, Vertex V1, Vertex V2); //消子
int GetVexPath(Vertex avPath[4]); //得到路徑,返回的是頂點數
bool IsBlank(int **pGameMap);
bool SearchValidPath(int** pGameMap);
void ResetGraph(int** pGameMap);
protected:
bool LinkInRow(int ** pGameMap,Vertex V1,Vertex V2); //判斷橫向是否連通
bool LinkInCol(int ** pGameMap, Vertex V1, Vertex V2); //判斷縱向是否連通
bool OneCornerLink(int ** pGameMap, Vertex V1, Vertex V2); //一個拐點連通判斷
bool LineY(int ** pGameMap, int nRow1, int nRow2, int nCol); //直接連通Y軸
bool LineX(int ** pGameMap, int nRow, int nCol1, int nCol2); //直接連通X軸
void PushVertex(Vertex V); //新增一個路徑頂點
void PopVertex(); //取出一個頂點
void ClearStack(); //清除棧
bool TwoCornerLink(int ** pGameMap, Vertex V1, Vertex V2); //三條直線消子判斷
- CGameDlg類
資料成員:
HICON m_hIcon;
CDC m_dcMem;
CDC m_dcBG;
CDC m_dcElement;
CDC m_dcMask;
CPoint m_ptGameTop;
CSize m_sizeElem;
CRect m_rtGameRect;
bool m_bFirstPoint;
CGameControl m_GameC;
CGameControl *m_GameControl;
CGameLogic *m_GameLogic;
int static GameTime;
bool m_bPlaying;
int nTime;
MCIDEVICEID m_DeviceID;
CProgressCtrl mProcess;
int GameType;
int Count;
成員函式:
void InitElement();
DECLARE_MESSAGE_MAP();
public:
afx_msg void OnPaint();
void UpdateWindow();
void InitBackground();
void UpdateMap();
CGameDlg *m_cGame;
virtual BOOL OnInitDialog();
void DrawTipFrame(int nRow, int nCol);
void DrawTipLine(Vertex asvPath[4], int Vexnum);
afx_msg void OnBnClickedSetting();
afx_msg void OnBnClickedStart();//開始遊戲
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);//
afx_msg void OnBnClickedTip();//提示
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnBnClickedStop();//暫停
afx_msg void OnBnClickedRepeat();//重排
afx_msg void OnBnClickedHelp();//幫助
afx_msg LRESULT CGameDlg::OnMciNotify(WPARAM wParam, LPARAM lParam);
afx_msg void OnNMCustomdrawProgress1(NMHDR *pNMHDR, LRESULT *pResult);
- CGameControl類
資料成員:
CGameLogic m_GameLogic; //遊戲邏輯操作物件
int ** m_pGameMap; //遊戲地圖陣列指標
Vertex m_svSelFst; //選中的第一個點
Vertex m_svSelSec; //選中的第二個點
static int s_nRows;
static int s_nCols;
static int s_nPicNum;
成員函式:
void StartGame();
int GetElement(int nRow,int nCol);
bool Link(Vertex avPath[4], int &nVexnum, bool flag); //消子判斷
bool IsWin();
void Reset( void );
void Help(Vertex tiPath[4], int &tiVexnum);
//不足
void SetFirstPoint(int nRow,int nCol); //設定第一個點
void SetSecPoint(int nRow, int nCol); //設定第二個點
2. 核心演算法設計
//遊戲地圖消子演算法
void CGameDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bPlaying == false)//如果遊戲不在執行不執行滑鼠響應
return;
bool bSuc;
int nRow = (point.y - m_ptGameTop.y) / m_sizeElem.cy;
int nCol = (point.x - m_ptGameTop.x) / m_sizeElem.cx;
//判斷滑鼠點選的區域
if (point.y<m_rtGameRect.top + m_ptGameTop.y || point.y>m_ptGameTop.y + CGameLogic::s_nRows*m_sizeElem.cy || point.x<m_rtGameRect.left + m_ptGameTop.x || point.x>m_ptGameTop.x + CGameLogic::s_nCols*m_sizeElem.cx || !m_bPlaying)
{
return CDialogEx::OnLButtonUp(nFlags, point);
}
if (m_GameC.m_pGameMap[nRow][nCol] >= 0)
DrawTipFrame(nRow, nCol);
if (m_bFirstPoint)
{
if (m_GameC.GetElement(nRow, nCol) != BLANK)
{
m_GameC.SetFirstPoint(nRow, nCol);
}
}
else {
if (m_GameC.GetElement(nRow, nCol) != BLANK)
{
m_GameC.SetSecPoint(nRow, nCol);
int nVexnum = 0;
Vertex avPath[4];
//連子判斷
bSuc = m_GameC.Link(avPath, nVexnum, true);
if (bSuc == true)
{
//畫提示線
DrawTipLine(avPath, nVexnum);
Sleep(150);
//更新地圖
UpdateMap();
}
InvalidateRect(false);
}
if (m_GameC.IsWin())
{
m_bPlaying = false;
CString str1, str2;
str1.Format(_T("遊戲結束"));
if (GameType != 2)
{
KillTimer(1);
mProcess.SetPos(GameTime);
CString str;
str.Format(_T("%d"), nTime);
GetDlgItem(IDC_STATIC)->SetWindowTextW(str);
str2.Format(_T("恭喜您!通關成功!用時%d秒!"), CGameDlg::GameTime - nTime);
if (GameType == 3)
{
str2.Format(_T("恭喜您!通關成功!用時%d秒!請進入下一關!"), CGameDlg::GameTime - nTime);
Count++;
m_GameC.m_GameLogic.PicNum++;
}
nTime = CGameDlg::GameTime;
}
else
str2.Format(_T("恭喜您!通關成功"));
MessageBox(str2, str1, MB_ICONINFORMATION);
GetDlgItem(IDC_Start)->EnableWindow(true);
}
}
m_bFirstPoint = !m_bFirstPoint;
}
//連子判斷
bool CGameControl::Link(Vertex avPath[4], int & nVexnum, bool flag)
{
//判斷是否同一張圖片
if (m_svSelFst.row == m_svSelSec.row&&m_svSelFst.col == m_svSelSec.col)
{
return false;
}
//判斷圖片是否相同
if (m_pGameMap[m_svSelFst.row][m_svSelFst.col] != m_pGameMap[m_svSelSec.row][m_svSelSec.col])
{
return false;
}
//判斷是否連通
if (m_GameLogic.IsLink(m_pGameMap, m_svSelFst, m_svSelSec))
{
//消子
if (flag)
m_GameLogic.Clear(m_pGameMap, m_svSelFst, m_svSelSec);
//返回路徑頂點
nVexnum = m_GameLogic.GetVexPath(avPath);
return true;
}
return false;
}
bool CGameControl::IsWin()
{
if (m_GameLogic.IsBlank(m_pGameMap))
{
return true;
}
return false;
}
//重排核心演算法
void CGameDlg::OnBnClickedRepeat()
{
// TODO: 在此新增控制元件通知處理程式程式碼
//獲取地圖大小和花色
int nRows = CGameLogic::s_nRows;
int nCols = CGameLogic::s_nCols;
int nPicNum = m_GameC.m_GameLogic.PicNum;
//設定種子
if (m_bPlaying)
{
srand((int)time(NULL));
//隨機任意交換兩個數字
int nVertexNum = nRows * nCols;
for (int i = 0; i < nVertexNum; i++)
{
//隨機得到兩個座標
int nIndex1 = rand() % nVertexNum;
int nIndex2 = rand() % nVertexNum;
//交換兩個數值
if (m_GameC.m_pGameMap[nIndex1 / nCols][nIndex1 % nCols] != BLANK && m_GameC.m_pGameMap[nIndex2 / nCols][nIndex2 % nCols] != BLANK)
{
int nTmp = m_GameC.m_pGameMap[nIndex1 / nCols][nIndex1%nCols];
m_GameC.m_pGameMap[nIndex1 / nCols][nIndex1%nCols] = m_GameC.m_pGameMap[nIndex2 / nCols][nIndex2%nCols];
m_GameC.m_pGameMap[nIndex2 / nCols][nIndex2%nCols] = nTmp;
}
}
m_dcMem.BitBlt(m_ptGameTop.x, m_ptGameTop.y, m_sizeElem.cx*nCols, m_sizeElem.cy*nRows, &m_dcMem, m_ptGameTop.x, m_ptGameTop.y, SRCINVERT);
m_dcMem.BitBlt(m_ptGameTop.x, m_ptGameTop.y, m_sizeElem.cx*nCols, m_sizeElem.cy*nRows, &m_dcBG, m_ptGameTop.x, m_ptGameTop.y, SRCPAINT);
UpdateMap();
}
}
3. 測試用例設計
介面設計
- 主介面佈局設計
- 遊戲介面佈局設計
- 遊戲地圖設計:用int型別的二維陣列儲存地圖中元素圖片的編號,起始點在客戶區的左上角,X軸向右為正,Y軸向下為正。
三.實驗結果
1.建立解決方案和工程:VS專案通常包括解決方案和工程。使用Visual Studio 2015開發工具,建立一個空的解決方案,解決方案名為LinkGame.sln。利用MFC應用程式嚮導,建立一個基於MFC對話方塊(Dialog)工程,工程名為LLK。
修改主介面對話方塊的屬性:
1.在使用應用程式嚮導工程時,選擇新增最小化按鈕
2.修改對話方塊的標題為“歡樂連連看”
3.用自定義的ico檔案替換預設的檔案,以修改對話方塊的圖示
除錯對話方塊的執行過程
2.主介面設計:選擇一張符合條件的BMP圖片作為背景,考慮主介面按鈕位置的擺放
1)點陣圖匯入
(1)將點陣圖資原始檔放到物理磁碟工程目錄下的res資料夾中。
(2)將點陣圖資源匯入到工程中。
(3)修改點陣圖資源為IDB_MAIN_BG。
2)繪製視窗背景
(1)建立一個記憶體DC。
(2)在CLLKDlg類新增void InitBackground()函式。
(3)載入點陣圖,建立相容DC。
(4)在CLLKDlg::OnInitDialog()函式中呼叫InitBackground()函式。
(5)呼叫CDC::BitBlt()函式,將點陣圖顯示在主介面上。
點陣圖的繪製流程
3)新增主介面的功能按鈕:
利用工具中,對話方塊編輯器的Mockup Image輔助功能,進行按鈕定位。
(1)給介面新增控制元件。
(2)修改按鈕文字(Caption)和ID。
(3)通過呼叫MoveWindow()函式設定主介面客戶區的大小。
(4)呼叫CenterWindow()函式,使視窗居中。
3.遊戲介面設計
1)新增遊戲對話方塊資源
2)建立並顯示對話方塊
(1)新增遊戲介面對話方塊類CGameDlg。
(2)建立並顯示遊戲對話方塊。
3)繪製遊戲介面背景
(1)載入遊戲介面背景圖片。
(2)將圖片選入點陣圖記憶體。
(3)將圖片從點陣圖記憶體拷貝到視訊記憶體。
(4)新增CGameDlg::UpdateWindow()函式,調整遊戲視窗大小。
4)遊戲介面佈局
(1)設定遊戲介面對話方塊標題。
(2)設定遊戲介面對話方塊圖示。
(3)新增控制元件。
除錯執行
4.繪製遊戲地圖
1)載入遊戲元素圖片
(1)將遊戲元素圖片載入到程式中。
(2)新增CGameLogic類。
(3)在CGameLogic類中新增初始化遊戲地圖函式。
(4)在CGameLogic類中建立釋放遊戲地圖函式
(5)呼叫初始化遊戲地圖函式,並進行異常處理。
(6)生成地圖資料。
2)繪製遊戲地圖
(1)呼叫CGameControl類中的GetElement()獲取相應行列位置圖片的元素編號值,並將對應編號的圖片區域的資料繪製到m_dcMem中的相應位置。
(2)遊戲地圖的起始點為客戶區中的(20,50)。遊戲地圖分為10行16列,由CGameControl類的靜態成員變數s_nRows和s_nCols得到。每格的大小和元素圖片一致,每個元素大小一致。
(3)在CGameDlg類中定義UpdateMap()函式,繪製遊戲介面。
(4)在繪製遊戲地圖之後,呼叫InvalidateRect()函式,更新遊戲區域。
3.)消除元素圖片背景
4)程式優化,將繪製遊戲介面的程式碼和設定遊戲視窗位置封裝為單獨的函式
5.同色消子
1)新增滑鼠事件
2)選擇圖片
(1)判斷點選位置是否在遊戲地圖中。
(2)計算滑鼠點選位置的行號和列號。
(3)在滑鼠選中的圖周圍繪製矩形提示框。
3)消除相同元素圖片
6.程式結構調整:按三層結構的思路,對程式的結構進行設計和修改:表示層、業務邏輯層、資料儲存層
1)程式結構設計
2)Vertex結構體的定義:程式中CGameLogic類和CGameControl類之間傳遞的資訊為該結構體變數
3)編寫CGameLogic類
4)編寫CGameControl類
5)編寫CGameDlg類
7.消子判斷
1)一條直線消子
(1)新增IsLink函式進行連通判斷。
(2)行號相同時,判斷橫向是否連通。
(3)列號相同時,判斷是否縱向連通。
2)兩條直線消子
(1)判斷橫向、縱向的線段是否能夠連通。
(2)判斷(nRow1,nCol1)到(nRow2,nCol2)能否連通。
(3)在CGameLogic::IsLink()中呼叫CGameDlg::OneCornerLink(),判斷能否進行兩條直線消子。
3)三條直線消子
在CGameLogic::TwoCornerLink()函式中,判斷能否進行三條直線消子。
4)繪製連通線
(1)判斷選擇的圖片是否為同一種圖片。
(2)對選中的兩張圖片進行連通判斷。
(3)獲取連線路徑。
(4)繪製連線線。
8.判斷勝負
1)判斷勝負
2)控制開始遊戲按鈕狀態
9.提示
1)邏輯層實現提示功能
2)控制層實現提示功能
3)介面層實現提示功能
10.重排
1)隨機開局
2)邏輯層實現重排功能
3)控制層實現重排功能
4)表示層實現重排功能
5)調整地圖大小
11.計時
1)新增進度條
2)新增計時器
3)顯示時間
4)判斷勝負
5)暫停遊戲
編譯執行程式。
實驗結果部分截圖如下
1.主介面:程式啟動時,出現系統的主介面
2.進入基本模式
3.開始遊戲介面
4.消子
完整程式碼見https://download.csdn.net/download/gyx1549624673/10637456