1. 程式人生 > >歡樂連連看小遊戲製作

歡樂連連看小遊戲製作

之前完成了歡樂連連看的實驗,現在來做一下總結,以實驗的步驟為綱進行。

一.實驗目的和要求

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;

 

核心類設計

  1. 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); //三條直線消子判斷

  1. 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);

  1. 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. 測試用例設計

介面設計

  1. 主介面佈局設計

 

 

 

 

  1. 遊戲介面佈局設計

     

 

  1. 遊戲地圖設計:用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