C語(yǔ)言實(shí)現(xiàn)經(jīng)典掃雷小游戲完整代碼(遞歸展開(kāi)?+?選擇標(biāo)記)
大家好,今天我們將一起用C語(yǔ)言實(shí)現(xiàn)一個(gè)經(jīng)典小游戲 – 掃雷,Let is go !
游戲介紹
掃雷游戲相信大家都玩過(guò),上圖就是一個(gè)網(wǎng)頁(yè)版的掃雷,它的規(guī)則是玩家選擇一個(gè)方格,若此方格沒(méi)有地雷,那么該方格會(huì)顯示與它相鄰的八個(gè)方格中雷的個(gè)數(shù),若此方格有地雷,那么游戲失敗,當(dāng)玩家把除了有地雷的方格外的其他方格都成功翻開(kāi)時(shí),游戲勝利。
游戲整體框架
對(duì)于一個(gè)代碼量還算可以的小游戲我們還是利用多文件來(lái)進(jìn)行編程,養(yǎng)成良好習(xí)慣,為以后在公司團(tuán)隊(duì)合作編程打下基礎(chǔ),因此我們把掃雷游戲分成三個(gè)文件來(lái)編寫(xiě):
test.c:游戲邏輯的測(cè)試,包含游戲菜單的打印,游戲設(shè)計(jì)的基本邏輯的展示。
game.c:游戲功能的具體實(shí)現(xiàn),這部分是整個(gè)游戲的核心代碼,一般不會(huì)展示給用戶。
game.h:相關(guān)頭文件的包含、符號(hào)的聲明以及函數(shù)的聲明。
游戲具體功能及實(shí)現(xiàn)
1、雷盤(pán)的定義
對(duì)于掃雷游戲,我們遇到的第一個(gè)問(wèn)題就是:應(yīng)該如何表示掃雷的雷盤(pán)及如何存放布雷、排雷的數(shù)據(jù);我們發(fā)現(xiàn),二維數(shù)組可以很好的解決這個(gè)問(wèn)題。
如上圖:我們定義了兩個(gè)棋盤(pán),分別用來(lái)保存布置雷的信息和排查雷的信息,這樣就可以避免二者相互干擾或者相互覆蓋;
同時(shí),我們使用宏來(lái)定義雷盤(pán)的大小以及雷的個(gè)數(shù),這樣做的好處是當(dāng)我們以后想使用更大的雷盤(pán)或者想增加掃雷的難度的時(shí)候,我們只需要改動(dòng)這里一次即可,增加了代碼的可維護(hù)性。
另外,很多小伙伴可能會(huì)疑惑為什么我這里會(huì)定義兩個(gè)不同ROW和COL,這其實(shí)是為后面的排雷做鋪墊:
如圖:當(dāng)我們排查1位置時(shí),如果1處不是雷,那么我們就會(huì)依次檢查1周?chē)?個(gè)坐標(biāo)是否有地雷,如果有,就會(huì)把地雷的數(shù)量顯示在1位置處;但是當(dāng)我們排查2位置時(shí),我們發(fā)現(xiàn), 數(shù)組排查雷時(shí)會(huì)發(fā)生越界,所以為了避免數(shù)組越界,我們就需要增加一系列限制條件,這樣做無(wú)疑是比較麻煩的,所以有的大佬就想出了這樣一種辦法:在定義數(shù)組長(zhǎng)度時(shí)我們直接在上下左右四個(gè)方向各多給一行的空間,并把這些空間中的數(shù)據(jù)初始化為非雷,這樣,就輕松解決了數(shù)組越界的問(wèn)題,不得不說(shuō),這種方法實(shí)在巧妙!
2、雷盤(pán)的初始化
最開(kāi)始的時(shí)候我們把mine數(shù)組元素全部初始化為字符0,把show數(shù)組元素全部初始化為字符*(給用戶一種神秘的感覺(jué))。
3、布置雷
對(duì)于布置雷我們有兩個(gè)需要注意的地方:
第一是用于隨機(jī)生成坐標(biāo)的rand函數(shù)的種子srand函數(shù)只需要在main函數(shù)中聲明一次即可。
第二是我們?cè)诓贾美椎臅r(shí)候需要檢查該位置是否已經(jīng)有雷,避免重復(fù)布置。
4、排查雷
排查雷的時(shí)候我們首先需要讓用戶輸入需要排查的坐標(biāo),然后判斷坐標(biāo)的合法性及該坐標(biāo)是否已被排查,其次再判斷該坐標(biāo)是否有雷,如果沒(méi)有,就遞歸檢查它周?chē)淖鴺?biāo),直到遇到有雷的坐標(biāo)才停止遞歸,再讓用戶選擇是否需要標(biāo)記雷的信息,最后檢查是否滿足游戲勝利的條件。
5、遞歸式展開(kāi)一片
觀察網(wǎng)頁(yè)版的掃雷我們可以發(fā)現(xiàn),當(dāng)用戶點(diǎn)擊一個(gè)坐標(biāo),如果該坐標(biāo)及其周?chē)淖鴺?biāo)都沒(méi)有雷,那么雷盤(pán)就會(huì)一次性展開(kāi)一片,而這樣設(shè)計(jì)也是比較合理的,因?yàn)槿绻恳粋€(gè)非雷坐標(biāo)都需要玩家排查的話十分影響游戲體驗(yàn);所以,這里我們就利用遞歸的實(shí)現(xiàn)模擬實(shí)現(xiàn)了這個(gè)功能。
6、獲取周?chē)椎膫€(gè)數(shù)
7、標(biāo)記特定位置
同樣:在網(wǎng)頁(yè)版的掃雷中,如果我們確定某一位置一定是雷時(shí),我們可以利用標(biāo)記功能來(lái)標(biāo)識(shí)該坐標(biāo),方便我們后面的判斷。
本代碼中,我們用字符 ! 來(lái)標(biāo)識(shí)雷。
8、打印雷盤(pán)
游戲完整代碼
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() { //定義用于存放雷和顯示雷的數(shù)組 char mine[ROWS][COLS]; char show[ROWS][COLS]; //數(shù)組初始化 BoardInit(mine, ROWS, COLS, '0'); BoardInit(show, ROWS, COLS, '*'); //埋雷 SetMine(mine, ROW, COL); system("cls"); //清除菜單,美觀整潔 //打印雷盤(pán) //BoardPrint(mine, ROW, COL); //用于自己調(diào)試觀察,在發(fā)布時(shí)注釋掉 BoardPrint(show, ROW, COL); //排雷 FindMine(mine, show, ROW, COL); } int main() { //設(shè)置隨機(jī)數(shù)的種子 srand((unsigned int)time(NULL)); int input = 0; do { menu();//菜單 printf("請(qǐng)選擇:"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戲!\n"); break; default: printf("輸入錯(cuò)誤,請(qǐng)重新輸入!\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 //數(shù)組初始化 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); //打印雷盤(pán) void BoardPrint(char board[ROWS][COLS], int row, int col);
3、game.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //數(shù)組初始化 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表示要初識(shí)化的字符 } } } //埋雷 void SetMine(char board[ROWS][COLS], int row, int col) { int count = MINE_COUNT; while (count) { int x = rand() % row + 1; //隨機(jī)生成雷的坐標(biāo) int y = rand() % col + 1; if (board[x][y] == '0') //檢查該位置是否已經(jīng)有雷 { board[x][y] = '1'; count--; } } } //打印雷盤(pán) void BoardPrint(char board[ROWS][COLS], int row, int col) { int i = 0; int j = 0; printf("------掃雷游戲------\n"); for (i = 0; i <= row; i++) //打印行號(hào) printf("%d ", i); printf("\n"); for (i = 1; i <= row; i++) { printf("%d ", i); //打印列號(hào) for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("------掃雷游戲------\n"); } //標(biāo)記雷的位置 void MarkMine(char board[ROWS][COLS], int row, int col) { int x = 0; int y = 0; while (1) { printf("請(qǐng)輸入你想要標(biāo)記位置的坐標(biāo)->"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) //判斷該坐標(biāo)是否合法 { if (board[x][y] == '*') //判斷該坐標(biāo)是否被排查 { board[x][y] = '!'; break; } else { printf("該位置不能被標(biāo)記,請(qǐng)重新輸入!\n"); } } else { printf("輸入錯(cuò)誤,請(qǐng)重新輸入!\n"); } } } //獲取坐標(biāo)周?chē)椎膫€(gè)數(shù) 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; } //遞歸爆炸式展開(kāi)一片 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) //判斷坐標(biāo)是否為排查范圍內(nèi) { int num = GetMineCount(mine, x, y); //獲取坐標(biāo)周?chē)椎膫€(gè)數(shù) if (num == 0) { (*pw)++; show[x][y] = ' '; //如果該坐標(biāo)周?chē)鷽](méi)有雷,就把該坐標(biāo)置成空格,并向周?chē)藗€(gè)坐標(biāo)展開(kāi) 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] == '*') //限制遞歸條件,防止已經(jīng)排查過(guò)的坐標(biāo)再次遞歸,從而造成死遞歸 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; //用來(lái)標(biāo)記是否取得勝利 int* pw = &win; char ch = 0; //用來(lái)接受是否需要標(biāo)記雷 while (win < row * col - MINE_COUNT) { printf("請(qǐng)輸入你想要排查的坐標(biāo)->"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) //判斷坐標(biāo)合法性 { if (mine[x][y] == '1') { system("cls"); printf("很遺憾,你被炸死了!\n"); BoardPrint(mine, row, col); //被炸死了就打印mine數(shù)組,讓用戶知道自己怎么死的 break; } else { if (show[x][y] != '*') //判斷是否重復(fù)排查 { printf("該坐標(biāo)已被排查,請(qǐng)重新輸入!\n"); continue; //直接進(jìn)入下一次循環(huán) } else { ExplosionSpread(mine, show, row, col, x, y, pw); //爆炸展開(kāi)一片 system("cls"); //清空屏幕 BoardPrint(show, row, col); //打印棋盤(pán) printf("需要標(biāo)記雷的位置請(qǐng)輸入y/Y,否則請(qǐng)按任意鍵->"); while ((ch = getchar()) != '\n'); //清理緩沖區(qū) scanf("%c", &ch); if (ch == 'Y' || ch == 'y') { MarkMine(show, row, col); //標(biāo)記雷 system("cls"); BoardPrint(show, row, col); } else { continue; } } } } else { printf("輸入錯(cuò)誤,請(qǐng)重新輸入!\n"); } } if (win == row * col - MINE_COUNT) { system("cls"); printf("恭喜你,排雷成功!\n"); BoardPrint(show, row, col); return; } }
游戲效果展示
到此這篇關(guān)于C語(yǔ)言小項(xiàng)目之掃雷游戲完整代碼(遞歸展開(kāi) + 選擇標(biāo)記)的文章就介紹到這了,更多相關(guān)C語(yǔ)言掃雷游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
15種?C++?常見(jiàn)報(bào)錯(cuò)原因分析
這篇文章主要介紹了15種?C++?常見(jiàn)報(bào)錯(cuò),本文通過(guò)實(shí)例代碼給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01C++實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)最新版
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)最新版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06OpenCV利用對(duì)比度亮度變換實(shí)現(xiàn)水印去除
OpenCV中去除水印最常用的方法是inpaint,通過(guò)圖像修復(fù)的方法來(lái)去除水印。本文將介紹另一種方法:利用對(duì)比度亮度變換去除水印,需要的朋友可以參考一下2021-11-11C/C++題解LeetCode1295統(tǒng)計(jì)位數(shù)為偶數(shù)的數(shù)字
這篇文章主要為大家介紹了C/C++題解LeetCode1295統(tǒng)計(jì)位數(shù)為偶數(shù)的數(shù)字示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01用C實(shí)現(xiàn)添加和讀取配置文件函數(shù)
本篇文章是對(duì)用C語(yǔ)言實(shí)現(xiàn)添加和讀取配置文件函數(shù)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C++實(shí)現(xiàn)求動(dòng)態(tài)矩陣各元素的和
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)求動(dòng)態(tài)矩陣各元素的和,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10