C語言實現經典掃雷小遊戲完整代碼(遞歸展開 + 選擇標記)

大傢好,今天我們將一起用C語言實現一個經典小遊戲 – 掃雷,Let is go !

遊戲介紹

掃雷遊戲相信大傢都玩過,上圖就是一個網頁版的掃雷,它的規則是玩傢選擇一個方格,若此方格沒有地雷,那麼該方格會顯示與它相鄰的八個方格中雷的個數,若此方格有地雷,那麼遊戲失敗,當玩傢把除瞭有地雷的方格外的其他方格都成功翻開時,遊戲勝利。

遊戲整體框架

對於一個代碼量還算可以的小遊戲我們還是利用多文件來進行編程,養成良好習慣,為以後在公司團隊合作編程打下基礎,因此我們把掃雷遊戲分成三個文件來編寫:

test.c:遊戲邏輯的測試,包含遊戲菜單的打印,遊戲設計的基本邏輯的展示。

game.c:遊戲功能的具體實現,這部分是整個遊戲的核心代碼,一般不會展示給用戶。

game.h:相關頭文件的包含、符號的聲明以及函數的聲明。

遊戲具體功能及實現

1、雷盤的定義

對於掃雷遊戲,我們遇到的第一個問題就是:應該如何表示掃雷的雷盤及如何存放佈雷、排雷的數據;我們發現,二維數組可以很好的解決這個問題。

如上圖:我們定義瞭兩個棋盤,分別用來保存佈置雷的信息和排查雷的信息,這樣就可以避免二者相互幹擾或者相互覆蓋;

同時,我們使用宏來定義雷盤的大小以及雷的個數,這樣做的好處是當我們以後想使用更大的雷盤或者想增加掃雷的難度的時候,我們隻需要改動這裡一次即可,增加瞭代碼的可維護性。

另外,很多小夥伴可能會疑惑為什麼我這裡會定義兩個不同ROW和COL,這其實是為後面的排雷做鋪墊:

如圖:當我們排查1位置時,如果1處不是雷,那麼我們就會依次檢查1周圍8個坐標是否有地雷,如果有,就會把地雷的數量顯示在1位置處;但是當我們排查2位置時,我們發現, 數組排查雷時會發生越界,所以為瞭避免數組越界,我們就需要增加一系列限制條件,這樣做無疑是比較麻煩的,所以有的大佬就想出瞭這樣一種辦法:在定義數組長度時我們直接在上下左右四個方向各多給一行的空間,並把這些空間中的數據初始化為非雷,這樣,就輕松解決瞭數組越界的問題,不得不說,這種方法實在巧妙!

2、雷盤的初始化

最開始的時候我們把mine數組元素全部初始化為字符0,把show數組元素全部初始化為字符*(給用戶一種神秘的感覺)。

3、佈置雷

對於佈置雷我們有兩個需要註意的地方:

第一是用於隨機生成坐標的rand函數的種子srand函數隻需要在main函數中聲明一次即可。

第二是我們在佈置雷的時候需要檢查該位置是否已經有雷,避免重復佈置。

4、排查雷

排查雷的時候我們首先需要讓用戶輸入需要排查的坐標,然後判斷坐標的合法性及該坐標是否已被排查,其次再判斷該坐標是否有雷,如果沒有,就遞歸檢查它周圍的坐標,直到遇到有雷的坐標才停止遞歸,再讓用戶選擇是否需要標記雷的信息,最後檢查是否滿足遊戲勝利的條件。

5、遞歸式展開一片

觀察網頁版的掃雷我們可以發現,當用戶點擊一個坐標,如果該坐標及其周圍的坐標都沒有雷,那麼雷盤就會一次性展開一片,而這樣設計也是比較合理的,因為如果每一個非雷坐標都需要玩傢排查的話十分影響遊戲體驗;所以,這裡我們就利用遞歸的實現模擬實現瞭這個功能。

6、獲取周圍雷的個數

7、標記特定位置

同樣:在網頁版的掃雷中,如果我們確定某一位置一定是雷時,我們可以利用標記功能來標識該坐標,方便我們後面的判斷。

本代碼中,我們用字符 ! 來標識雷。

8、打印雷盤

image-20220510174033521

遊戲完整代碼 

1、test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
	printf("*****************************************\n");
	printf("*********  1.play      0.exit   *********\n");
	printf("*****************************************\n");
}
void game()
{
	//定義用於存放雷和顯示雷的數組
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	//數組初始化
	BoardInit(mine, ROWS, COLS, '0');
	BoardInit(show, ROWS, COLS, '*');
	//埋雷
	SetMine(mine, ROW, COL);
	system("cls");   //清除菜單,美觀整潔
	//打印雷盤
	//BoardPrint(mine, ROW, COL);   //用於自己調試觀察,在發佈時註釋掉
	BoardPrint(show, ROW, COL);
	//排雷
	FindMine(mine, show, ROW, COL);
}
int main()
{
	//設置隨機數的種子
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();//菜單
		printf("請選擇:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出遊戲!\n");
			break;
		default:
			printf("輸入錯誤,請重新輸入!\n");
			break;
		}
	} while (input);
	return 0;
}

2、game.h

#pragma once
#include<stdio.h>
#include<windows.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define MINE_COUNT 10
//數組初始化
void BoardInit(char board[ROWS][COLS], int rows, int cols, char set);
//埋雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//打印雷盤
void BoardPrint(char board[ROWS][COLS], int row, int col);

3、game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//數組初始化
void BoardInit(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;   //set表示要初識化的字符
		}
	}
}
//埋雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = MINE_COUNT;
	while (count)
	{
		int x = rand() % row + 1;      //隨機生成雷的坐標
		int y = rand() % col + 1;
		if (board[x][y] == '0')        //檢查該位置是否已經有雷
		{
			board[x][y] = '1';
			count--;
		}
	}
}
//打印雷盤
void BoardPrint(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("------掃雷遊戲------\n");
	for (i = 0; i <= row; i++)   //打印行號
		printf("%d ", i);
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);   //打印列號
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("------掃雷遊戲------\n");
}
//標記雷的位置
void MarkMine(char board[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("請輸入你想要標記位置的坐標->");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)    //判斷該坐標是否合法
		{
			if (board[x][y] == '*')        //判斷該坐標是否被排查
			{
				board[x][y] = '!';
				break;
			}
			else
			{
				printf("該位置不能被標記,請重新輸入!\n");
			}
		}
		else
		{
			printf("輸入錯誤,請重新輸入!\n");
		}
	}
}
//獲取坐標周圍雷的個數
int GetMineCount(char board[ROWS][COLS], int x, int y)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if (board[i][j] == '1')
			{
				count++;
			}
		}
	}
	return count;
}
//遞歸爆炸式展開一片
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* pw)
{
	if (x >= 1 && x <= row && y >= 1 && y <= col)  //判斷坐標是否為排查范圍內
	{
		int num = GetMineCount(mine, x, y);   //獲取坐標周圍雷的個數
		if (num == 0)
		{
			(*pw)++;
			show[x][y] = ' ';   //如果該坐標周圍沒有雷,就把該坐標置成空格,並向周圍八個坐標展開
			int i = 0;
			int j = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				for (j = y - 1; j <= y + 1; j++)
				{
					if (show[i][j] == '*')    //限制遞歸條件,防止已經排查過的坐標再次遞歸,從而造成死遞歸
						ExplosionSpread(mine, show, row, col, i, j, pw);
				}
			}
		}
		else
		{
			(*pw)++;
			show[x][y] = num + '0';
		}
	}
}
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;  //用來標記是否取得勝利
	int* pw = &win;
	char ch = 0;   //用來接受是否需要標記雷
	while (win < row * col - MINE_COUNT)
	{
		printf("請輸入你想要排查的坐標->");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)   //判斷坐標合法性
		{
			if (mine[x][y] == '1')
			{
				system("cls");
				printf("很遺憾,你被炸死瞭!\n");
				BoardPrint(mine, row, col);   //被炸死瞭就打印mine數組,讓用戶知道自己怎麼死的
				break;
			}
			else
			{
				if (show[x][y] != '*')   //判斷是否重復排查
				{
					printf("該坐標已被排查,請重新輸入!\n");
					continue;  //直接進入下一次循環
				}
				else
				{
					ExplosionSpread(mine, show, row, col, x, y, pw);  //爆炸展開一片
					system("cls");  //清空屏幕
					BoardPrint(show, row, col);  //打印棋盤
					printf("需要標記雷的位置請輸入y/Y,否則請按任意鍵->");
					while ((ch = getchar()) != '\n');  //清理緩沖區
					scanf("%c", &ch);
					if (ch == 'Y' || ch == 'y')
					{
						MarkMine(show, row, col);   //標記雷
						system("cls");
						BoardPrint(show, row, col);
					}
					else
					{
						continue;
					}
				}
			}
		}
		else
		{
			printf("輸入錯誤,請重新輸入!\n");
		}
	}
	if (win == row * col - MINE_COUNT)
	{
		system("cls");
		printf("恭喜你,排雷成功!\n");
		BoardPrint(show, row, col);
		return;
	}
}

遊戲效果展示

到此這篇關於C語言小項目之掃雷遊戲完整代碼(遞歸展開 + 選擇標記)的文章就介紹到這瞭,更多相關C語言掃雷遊戲內容請搜索LevelAH以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持LevelAH!

推薦閱讀: