本文為我個人原創,首發於我的個人博客:http://migod.top/176.html,轉載請注明出處!
項目介紹
迷宮大師是本人的C++程序設計的大作業,是一個可視化的迷宮小游戲。可視化界面基於Qt5,使用Qt Creator開發。
項目主要有如下特點:
- 對Qt自帶的控件進行了二次封裝,以實現更加美觀的游戲效果;
- 為了更方便的設計關卡,配套開發了可視化的迷宮地圖編輯器,並實現了復雜迷宮地圖的深度優先生成;
- 迷宮游戲實現了文件的讀寫,實現了導出和讀取玩家自制地圖,並加入了通過DFS繪制迷宮出路的功能;
項目的設計樹形圖如下:
演示截圖
- 主界面
- 選擇關卡界面
- 游玩界面
- 游玩界面 – 自動尋路
(注明:左下角的終點無法到達,尋找到了右上角的終點)
- 游玩界面 – 勝利/失敗
- 編輯地圖界面(可以用鼠標點擊Tile來更改地圖圖塊的狀態)
- 編輯地圖界面 – 隨機生成
開發思路
使用Qt GUI框架
Qt是一個成熟的跨平台的C++圖形用戶界面應用程序框架。選擇Qt的作為GUI框架相對於選擇OpenGL可以節省很多不必要的開發時間,提升開發效率的同時保證穩定性。Qt提供了一些工具類,比如QString,QDebug等。筆者選擇使用Qt自帶的這些類而不是C++本身提供的string,cin,cout這些工具類,以保證程序內部信息流交互更加方便。
封裝圖片按鈕類 ImgButton
我們手動封裝一個自己的圖片按鈕類:ImgButton。這個類繼承QPushButton。
利用這個類和筆者自己用Photoshop制作的按鈕圖片素材,我們可以實現比Qt自帶的QPushButton更美觀的按鈕。筆者還為按鈕添加了音效。
筆者利用Photoshop自己制作的部分按鈕素材如下(具體見源代碼res文件夾):
主窗口(標題界面)MainWindow
主窗口將選關窗口和地圖編輯窗口作為數據成員,便於進行與這些界面間的切換。
封裝地圖圖塊類 Tile
我們繼承QPushButton封裝一個地圖圖塊類Tile,之后利用這個類和圖片素材繪制迷宮地圖。利用網上的圖像素材,筆者制作的地圖圖塊如下(按順序為牆、路、起點、終點):




迷宮數據工具類 MazeData
設計迷宮數據文件:Unicode編碼的文本文件,第一行n為迷宮大小(迷宮寬高相等),之后n行是迷宮地圖信息。
如:
11
03000000000
01111111110
00000000010
01111111010
01000001010
01011111010
01010001010
01010211010
01010000010
01011111110
00000000000
為了方便我們在編輯窗口和游玩窗口快速讀寫迷宮文件,筆者設計了一個迷宮數據工具類MazeData。我們利用二維數組保存迷宮數據。
地圖編輯窗口 MapEditWindow
加入地圖編輯窗口,既方便筆者自己制作迷宮關卡,也為玩家提供了擴展游戲玩法和難度的可能性。通過筆者封裝的Tile類,可以實現鼠標點擊繪制。
地圖編輯窗口類包含了本程序的核心難點之一:迷宮地圖深度優先生成算法。這個算法使用在此類的createMaze成員函數里。算法的偽代碼如下:
設置 迷宮內所有點 為 牆壁
設置 (1,1)點 為 路
把 (1,1)點 入棧
設置 迷宮連通標志 為 未連通
如果 迷宮連通標志 為 未連通,執行循環:
設置 當前點 為 棧內最后一個點 #因為最后一個儲存的是最深的
尋找 當前點 潛在的鄰居 #潛在鄰居上下左右的牆都是未打通的,且和當前點只隔了一個牆
如果 存在潛在的鄰居:
設置 任意一個 潛在的鄰居 為 真鄰居
打通 當前點 與 真鄰居 之間的牆
把 真鄰居 入棧
否則:#該路徑沒有合適的鄰居,去除最后一個不滿足要求的,往上一個點尋找
出棧
判斷地圖是否連通,把結果儲存到 迷宮連通標志
算法流程圖如下:
選關窗口 ChooseLevelWindow
選關窗口將游玩窗口為該類的數據成員,便於窗口間切換。除了內置關卡,筆者在選關窗口增加了讀取地圖的按鈕,以期拓展游戲的可玩性。
游玩窗口 PlayWindow
游玩窗口是游戲的核心窗口。游玩窗口實現玩家移動功能、判定是否存在通路功能、繪制通路功能、游戲倒計時功能,還增加了成功或者失敗時的簡單動畫。
地圖編輯窗口類包含了本程序的另一個難點:深度優先搜索迷宮通路(筆者采用了利用棧的非遞歸的實現)。這個算法使用在此類的findPosssibleWay成員函數里。算法的思路如下:
- 首先起點入棧
pos_stack
,只要pos_stack
中有值,說明還有待遍歷的位置,繼續遍歷 - 進入循環體,說明該點被遍歷,該點加入
possible_path
棧 - 判斷該點是否為出口,如果是,已經發現一條可行路線,返回true`,函數結束
- 如果循環向下執行,說明不是終點,將該點標記為已經走過
- 從該點探索與該點連接的,其他可走的位置,入棧
- 如果沒有任何點入棧,說明是死路或者正在回退中,進行一步回溯,即出棧
- 如果離開循環體,說明沒有通路,返回false
以上為各模塊功能介紹及設計思路,具體實現見源代碼。
總結
1、大作業完成過程中存在的問題、解決方法以及我的心得體會
在設計迷宮大師時,我計划要添加一個GUI的迷宮編輯的功能,但一開始一直沒有想清楚要如何實現非文本輸入的方式編輯迷宮。后來我在對Qt有一定了解之后,我發現我可以對QPushButton做一次二次封裝,做成我自己的地圖圖塊。這個圖塊可以開啟或者關閉按鈕功能,如果開啟按鈕功能,點擊就能更換為其他狀態(比如從牆變成路),於是問題就迎刃而解了。
在程序的地圖繪制方面,我利用采取循環動態生成這個自己封裝的地圖圖塊Tile,並把圖塊保存在了31*31的二維數組里。實際上這個部分可以改用二維Vector或者QVector保存,可以在節省內存空間的同時解除地圖大小的限制。
迷宮尋路采用常規的DFS,並且嘗試使用了少見的非遞歸DFS解決了問題。
迷宮生成算法一開始沒有思路,但是在網上查閱之后,我發現深度優先生成迷宮算法的效果比較好,於是在理解了算法之后將其添加到了程序里,實現了在編輯窗口自動生成迷宮的功能。
2、查閱文獻的過程
我是從零開始學習Qt GUI框架的,所以一開始並沒有急着開始着手迷宮大師的制作,而是先進行對Qt基本語法的學習。我購買了一本《Qt5開發及實例》,在快速看完了前五章之后才開始着手制作自己的迷宮程序。
另外,我在開發時也遇到了C++上的一些問題,這些問題我除了直接搜索,還經常在C++ Reference這個英文網站(http://www.cplusplus.com/reference/)上進行參考查閱。C++ Reference對C++各個類和功能的完整說明給了我很大幫助。在學習Qt時,我還發現我對Qt經常使用的C++ lambda表達式不太熟練,遇上我查閱了《C++ Primer》10.3.2.節 Lambda Expressions的內容——這對我理解C++ 11的lambda表達式起到了一定幫助。
除此之外,Qt官方的Qt5說明文檔也是我在完成大作業時參考頻率非常高的文獻。
3、通過本門課程實驗和大作業,自己對軟件維護的認識
(1) 面向對象的程序設計思想是很重要的。設計程序時,根據現實情況將程序封裝成一個個對象,能夠有效地幫助我梳理程序結構;並且由於面向對象后的class對功能的封裝,提高了我的代碼復用率和可擴充性,而且讓我在添加新功能的時候能夠減少一些不必要的考慮。
(2) 代碼注釋是很有必要的。對代碼進行規范的注釋,可以讓程序員可以更好的理解代碼的思路和用途。在本次大作業中,我進行了大量標注,尤其是在涉及到核心算法的部分更是有詳細標注,這對我進行功能擴展和后期Debug起到了很大幫助。
(3) 良好的縮進與代碼風格可以減少我寫代碼時的閱讀疲勞。
源代碼及可執行程序下載/Clone
github:
https://github.com/migodz/Qt-MazeMaster
如果這個項目有幫助到你,還請點個Star,拜托啦!