Qt 掃雷遊戲設計(一)

這次打算以掃雷遊戲,做一個Qt界面設計的總結過程

這第一篇以介紹掃雷的算法,實現一個控制檯版本爲起點

先來查看掃雷遊戲的程序設計,玩法就不介紹了:

1、首先便是要隨機生成地雷,可以利用rand生成

這裏以一個簡單的9*9的一維數組模擬2維數組作爲存儲結構,以簡單的幾個數字來標記每個方塊的狀態
-1表示有雷
0-8表示周圍八個方塊的雷數情況
10表示右鍵點擊標記的真雷
11表示右鍵點擊標記的假雷,也就是猜錯了,其實這不是雷
12表示左鍵點擊的無雷區

這就是用來表示方塊數據的數據結構

2、每局開始之前,先遍歷一邊所有的方塊,將雷數緩存下來,這樣,之後再使用時可以減少計算,當地圖生成之後,這個就可以計算了

3、關於繪製圖像,即以左鍵點擊和右鍵點擊來決定繪製的圖片是怎樣的,不過作爲控制檯程序,這裏就是以下方式顯示

"*"表示未被點擊狀態

"F" 表示右鍵標記狀態,即假定這是個雷

" " 空格表示左鍵點擊,且此處無雷

4、關於左鍵點擊之後,即應該遍歷判斷點擊區域周圍的八個方向,若該方向被點擊或有雷則停止,若遍歷的方塊的雷數仍然爲0,則繼續遞歸判斷,直到不滿足條件,或超出邊界。


這裏關於掃雷的大體設計即介紹完畢,接下來以代碼演示看一下:

1、創建雷區

// 初始化8個雷
int map[9 * 9] = {0};
int minesTotal = 8;
for (int i = 0; i < minesTotal; ++i)
{
	map[i] = -1;
}

// 此函數由STL算法提供,可用於將數組亂序
random_shuffle(map, map + 9 * 9);
通過上述代碼,即構建好雷區,這裏利用random_shuffle來生成隨機地圖

2、迭代整個地圖,來緩存每個方塊存儲的雷數

// 預先計算,緩存雷數
for (int i = 0; i < 9 * 9; ++i)
{
	int row = i / 9;
	int col = i % 9;
	countMine(row, col, map);
}

現在查k難countMine函數,這裏先看一個最直接簡單的方式

int toIndex(int row, int col)
{
	return row * 9 + col;
}
// 這裏用最直接的方式,8個if,每個都計算一個鄰近方塊的有雷情況
void countMine(int row, int col, int *map)
{
	if (map[toIndex(row, col)] == -1)
		return;

	int nums = 0;
	if (row-1 >= 0 && col-1 >=0 && map[toIndex(row-1, col-1)] == -1)
		++nums;
	if (row-1 >= 0 && map[toIndex(row-1, col)] == -1)
		++nums;
	if (row-1 >= 0 && col+1 <=8 && map[toIndex(row-1, col+1)] == -1)
		++nums;
	if (col-1 >=0 && map[toIndex(row, col-1)] == -1)
		++nums;
	if (col+1 <=8 && map[toIndex(row, col+1)] == -1)
		++nums;
	if (row+1 <= 8 && col-1 >=0 && map[toIndex(row+1, col-1)] == -1)
		++nums;
	if (row+1 <= 8 && map[toIndex(row+1, col)] == -1)
		++nums;
	if (row+1 <= 8 && col+1 <=8 && map[toIndex(row+1, col+1)] == -1)
		++nums;
	map[toIndex(row, col)] = nums;
}

哈哈,這種方式,不動腦子哦,下面用循環改寫下

bool isMine(int row, int col, int *map)
{
	int index = toIndex(row, col);
	return (map[index] == -1)
}

bool isValidIndex(int row, int col)
{
	if (0 <= row && row < 9 && 0 <= col && col < 9)
		return true;
	return false;
}

void countMine(int row, int col, int *map)
{
	if (isMine(row, col, map))
		return;
	
	int minesCount = 0;
	for (int aroundRow = row - 1; aroundRow <= row + 1; ++aroundRow)
	{
		for (int aroundCol = col - 1; aroundCol <= col + 1; ++aroundCol)
		{
			// 判斷鄰近周圍8個方塊的是否有雷,來統計本方塊的雷數
			bool bvalidIndex = isValidIndex(aroundRow, aroundCol);
			bool bSameBlock = (row == aroundRow && col == aroundCol);
			bool bMine = isMine(aroundRow, aroundCol);
			if (bvalidIndex && !bSameBlock && isMine)
				++minesCount;
		}
	}
	map[toIndex(row, col)] = minesCount;
}

利用循環,改寫上述重複的if判斷

3、處理完成之後,應該遊戲開始,這裏即以一個死循環開始,滿足勝利或失敗條件即退出

while (true)
{
	// 輸出歡迎語
	cout << "\ninput the cordinate (like 5 5, & f for flag, e for empty)\n";
	// 獲取輸入
	int row, col;
	char ch;
	cin >> row >> col >> ch;
	
	// 檢查此次的輸入,應該達到的顯示效果
	if (checkMap(row - 1, col - 1, ch, map))
	{
		// 這裏不小心點到雷即退出遊戲,這裏滿足失敗條件
		cout << "\n                You Lose !!!!!!!!!!!!!!!!\n\n";
		break;
	}
		
	if (minesRemain == 0)
	{
		// 這裏滿足勝利條件
		cout << "\n                You Win !!!!!!!!!!!!!!!!!\n\n";
		break;
	}
		
	// 顯示打印地圖,此輪結束,繼續下輪
	showMap(map);
}
可以看到,整個遊戲的邏輯判斷,即位於checkMap中

checkMap很簡單,只是依據輸入,進行判斷,邏輯沒有複雜的,唯一就是在點擊無雷區時,需要判斷是否需要展開周圍雷區

// 返回真表示點到雷,掛掉
// 假則表示這次點擊正確
bool checkMap(int row, int col, char ch, int *map)
{
	bool status = false;
	if (isMine(row, col, map))
	{
		if (ch == 'f')
		{
			map[toIndex(row, col)] = 10;
			--minesRemain;
		}
		else if (ch == 'e')
		{
			status = true;
		}
	}
	else
	{
		if (ch == 'f')
		{
			map[toIndex(row, col)] = 11;
		}
		else if (ch == 'e')
		{
			map[toIndex(row, col)] = 12;
			checkAround(row, col, map);
		}
	}
	return status;
}

checkAround的實現,就如所countMines中一樣,這裏直接利用循環,遍歷鄰近8個方塊,然後繼續判斷是否需要進行遞歸

整個函數只有在該方塊的雷數爲0的情況下才需要進行遞歸判斷,若雷數不爲0,則不需要再去判斷周圍雷區情況。

void checkAround(int row, int col, int *map)
{
	if (map[toIndex(row, col)] == 0)
	{
		for (int aroundRow = row - 1; aroundRow <= row + 1; ++aroundRow)
		{
			for (int aroundCol = col - 1; aroundCol <= col + 1; ++aroundCol)
			{
				bool bvalidIndex = isValidIndex(aroundRow, aroundCol);
				bool bSameBlock = (row == aroundRow && col == aroundCol);
				if (bvalidIndex && !bSameBlock)
				{
					if (map[toIndex(aroundRow, aroundCol)] == 12)
						continue;
					
					// 設置此處被點擊,以後遍歷直接跳過
					map[toIndex(aroundRow, aroundCol)]= 12;
					checkAround(aroundRow, aroundCol, map);
				}
			}
		}
	}
}

至此,遊戲主體和邏輯已經介紹完畢,之後的打印函數,就不在顯示了。


下面給出,控制檯版本的代碼,這部分代碼已經把上述封裝成類,有興趣的可以前去下載。

http://download.csdn.net/detail/satanzw/5825767

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