C語言二維數(shù)組運(yùn)用實(shí)現(xiàn)掃雷游戲
作為80后、90后的老年人,想必對掃雷游戲都不陌生。
掃雷,是微軟在win8版本之前系統(tǒng)自帶的一款游戲——現(xiàn)在已經(jīng)被下架了,童年的回憶,很難受。
游戲操作很簡單,首先初始化一個(gè)m×n的方塊矩陣,然后玩家不斷地翻開方塊,直到所有沒有雷的方塊都被翻開,游戲勝利。
在玩家進(jìn)行操作的時(shí)候,游戲會適當(dāng)?shù)慕o予玩家一些提示:分布在雷周圍的方塊會顯示出數(shù)字提示周圍八個(gè)小方塊里有幾個(gè)雷,這樣,玩家就可以根據(jù)提示來排除那些藏了雷的小方塊。
現(xiàn)在,我們根據(jù)游戲規(guī)則來整理一下程序的思路:
1、創(chuàng)建兩個(gè)二維數(shù)組,分別保存雷的分布地圖和顯示給玩家的地圖;
2、分別初始化這兩個(gè)數(shù)組;
3、打印初始地圖
4、讀取玩家輸入的坐標(biāo)并對其進(jìn)行合法性判定;
5、若玩家的輸入合法, 對地圖進(jìn)行更新, 否則, 提示玩家重新輸入;
6、判定游戲是否結(jié)束, 若結(jié)束轉(zhuǎn)到6, 否則重復(fù)3~6
7、輸出游戲結(jié)果.
現(xiàn)在根據(jù)思路來寫一下程序大致的框架了.
首先, 要兩個(gè)二維數(shù)組, 那么問題來了, 數(shù)組要多大才合適呢? 顯然, 數(shù)組的大小要和游戲的規(guī)模大小相同——也就是方塊矩陣的大小和雷的數(shù)量,而這個(gè)矩陣多大、雷有多少,我們不知道,為了方便修改,我們使用宏定義來定義:
#define MAX_ROW 10?? ?//矩陣行數(shù) #define MAX_COL 10?? ?//矩陣列數(shù) #define MAX_MINE 10?? ?//雷的數(shù)量
然后創(chuàng)建兩個(gè)二維數(shù)組:
char mine_map[MAX_ROW][MAX_COL]; char game_map[MAX_ROW][MAX_COL];
mine_map是保存雷的分布數(shù)據(jù)的地圖,game_map則是顯示給用戶的地圖。
然后分別對這兩個(gè)地圖進(jìn)行初始化,game_map的初始化比較簡單:
void init_map(char a[MAX_ROW][MAX_COL]) { ?? ?for (int i = 0; i < MAX_ROW; i++) ?? ??? ?for (int j = 0; j < MAX_COL; j++) ?? ??? ??? ?a[i][j] = '#'; }
而mine_map的初始化則比較復(fù)雜了。首先,需要隨機(jī)生成MAX_MINE個(gè)雷,這里我們使用rand()函數(shù)來生成,并在初始化之前生成一個(gè)隨機(jī)種子。接著,在生成完MAX_MINE個(gè)雷之后,對mine_map掃描一次,在雷的周圍的小方塊里生成提示數(shù)字:
//計(jì)算一個(gè)小方塊的周圍有幾個(gè)雷的函數(shù) void cal_mine(char a[MAX_ROW][MAX_COL], int row, int col) { ?? ?for (int r = row - 1; r < row + 2; r++) ?? ??? ?for (int c = col - 1; c < col + 2; c++) ?? ??? ?{ ?? ??? ??? ?if (r >= 0 && r < MAX_ROW ?? ??? ??? ??? ?&& c >= 0 && c < MAX_COL ?? ??? ??? ??? ?&& a[r][c] != '*') ?? ??? ??? ??? ?a[r][c] = a[r][c] + 1; ?? ??? ?} } //生成游戲地圖 void create_map(char map[MAX_ROW][MAX_COL]) { ?? ?int count = 0; ?? ?int row; ?? ?int col; ?? ?for (int i = 0; i < MAX_ROW; i++) ?? ??? ?for (int j = 0; j < MAX_COL; j++) ?? ??? ??? ?map[i][j] = '0'; ?? ?while (count < MAX_MINE) ?? ?{ ?? ??? ?row = rand() % MAX_ROW; ?? ??? ?col = rand() % MAX_COL; ?? ??? ?if (map[row][col] != '*') ?? ??? ??? ?map[row][col] = '*'; ?? ??? ?else ?? ??? ??? ?continue; ?? ??? ?count += 1; ?? ?} ?? ?for (int i = 0; i < MAX_ROW; i++) ?? ??? ?for (int j = 0; j < MAX_COL; j++) ?? ??? ?{ ?? ??? ??? ?if (map[i][j] == '*') ?? ??? ??? ??? ?cal_mine(map, i, j); ?? ??? ?} }
之后就是更新地圖和打印地圖的函數(shù)了,為了方便調(diào)試上面這兩個(gè)初始化函數(shù),我們先寫打印地圖函數(shù):
void display_map(char gmap[MAX_ROW][MAX_COL]) { ?? ?system("cls"); ?? ?for (int r = MAX_ROW - 1; r >= 0; r--) ?? ?{ ?? ??? ?printf(" %2d|", r + 1); ?? ??? ?for (int c = 0; c < MAX_COL; c++) ?? ??? ??? ?printf(" %c", gmap[r][c]); ?? ??? ?printf("\n"); ?? ?} ?? ?printf(" ? |"); ?? ?for (int i = 0; i < MAX_COL; i++) ?? ??? ?printf("__"); ?? ?printf("\n ? ? "); ?? ?for (int i = 0; i < MAX_COL; i++) ?? ??? ?printf("%-2d", i + 1); ?? ?printf("\n"); }
這個(gè)函數(shù)的可讀性并不好,因?yàn)檫@個(gè)地圖長啥樣完全是根據(jù)我的喜好來定的,雖然用控制臺寫的程序都丑,但是它丑得有多別致由我決定······這個(gè)函數(shù)不同的人能玩出不同的花,以上僅供參考。
最后就是更新地圖函數(shù)了,先寫一個(gè)函數(shù)框架吧:
int renew_map(char game[MAX_ROW][MAX_COL],? ?? ??? ??? ?char mine[MAX_ROW][MAX_COL], ?? ??? ??? ? int row, int col) { ?? ?int count; ?? ?game[row][col] = mine[row][col]; ?? ?if (mine[row][col] == '*') ?? ??? ?return 0; ?? ?else if (mine[row][col] != '0') ?? ??? ?return 1; ?? ?count = continues_map(game, mine, row, col); ?? ?return count; }
如果讀到了雷,返回0;如果讀到了非零區(qū)域,則將其翻開;若讀到了周圍沒有雷的區(qū)域,則將其周圍的方塊自動翻開,直到不為0為止,這里我使用了一個(gè)叫continues_map的函數(shù)。
那么問題來了,要如何實(shí)現(xiàn)自動翻開的功能呢?
要回答這個(gè)問題,首先就要清楚連續(xù)翻的規(guī)則:在掃雷游戲中,提示數(shù)為0的方塊表示周圍沒有雷,因此這個(gè)方塊周圍的八個(gè)方塊都是可以翻開的,而且如果這八個(gè)方塊之中有提示數(shù)為0的方塊,則繼續(xù)將該方塊的周圍八個(gè)方塊翻開,直到碰到提示數(shù)不為0的方塊為止,并將其翻開······這樣來看,能否用循環(huán)語句來實(shí)現(xiàn)呢?我們想想算法思路:
1、判斷該方塊是否是“0”方塊. 如果是,則掃描該方塊的周圍8個(gè)小方塊;如果不是,則將該提示數(shù)錄入game_map中;
2、對這8個(gè)方塊分別執(zhí)行步驟1.
考慮一下極端情況,若這8個(gè)方塊也都是“0”方塊,那么是否能用循環(huán)繼續(xù)對這八個(gè)方塊分別掃描?就算可以,繼續(xù)考慮極端情況,能否繼續(xù)用循環(huán)對周圍16個(gè)方塊進(jìn)行掃描?隨著問題規(guī)模的增加,循環(huán)逐漸乏力,也就是說使用循環(huán)處理該問題并不好——就算用循環(huán)實(shí)現(xiàn)了,代碼也將會非常復(fù)雜,可讀性非常之差,不利于后期的維護(hù)和修改。
那應(yīng)該使用什么方法來實(shí)現(xiàn)?我們接著看算法思路,首先進(jìn)行判斷,如果條件滿足,則將原問題分解為8個(gè)子問題,再對這8個(gè)子問題進(jìn)行之前的操作,直到將所有滿足判斷條件的子問題處理完為止——這樣來看,這不就是遞歸調(diào)用的特性嘛。
于是,只要將該函數(shù)寫成遞歸形式不就行了嘛:
int continues_map(char game[MAX_ROW][MAX_COL], char mine[MAX_ROW][MAX_COL], int row, int col) { ?? ?int count = 1; ?? ?int left = col - 1, right = col + 1, up = row + 1, down = row - 1; ?? ?if (left >= 0 && game[row][left] == '#' && mine[row][left] == '0') ?? ?{ ?? ??? ?game[row][left] = '0'; ?? ??? ?count = count + continues_map(game, mine, row, left); ?? ?} ?? ?else if (left >= 0 && game[row][left] == '#') ?? ?{ ?? ??? ?game[row][left] = mine[row][left]; ?? ??? ?count++; ?? ?} ?? ?if (right < MAX_COL && game[row][right] == '#' && mine[row][right] == '0') ?? ?{ ?? ??? ?game[row][right] = '0'; ?? ??? ?count = count + continues_map(game, mine, row, right); ?? ?} ?? ?else if (right < MAX_COL && game[row][right] == '#') ?? ?{ ?? ??? ?game[row][right] = mine[row][right]; ?? ??? ?count++; ?? ?} ?? ?if (up < MAX_ROW && game[up][col] == '#' && mine[up][col] == '0') ?? ?{ ?? ??? ?game[up][col] = '0'; ?? ??? ?count = count + continues_map(game, mine, up, col); ?? ?} ?? ?else if (up < MAX_ROW && game[up][col] == '#') ?? ?{ ?? ??? ?game[up][col] = mine[up][col]; ?? ??? ?count++; ?? ?} ?? ?if (down >= 0 && game[down][col] == '#' && mine[down][col] == '0') ?? ?{ ?? ??? ?game[down][col] = '0'; ?? ??? ?count = count + continues_map(game, mine, down, col); ?? ?} ?? ?else if (down >= 0 && game[down][col] == '#') ?? ?{ ?? ??? ?game[down][col]= mine[down][col]; ?? ??? ?count++; ?? ?} ?? ?if (left >= 0 && game[up][left] == '#' && mine[up][left] == '0') ?? ?{ ?? ??? ?game[up][left] = '0'; ?? ??? ?count = count + continues_map(game, mine, up, left); ?? ?} ?? ?else if (left >= 0 && game[up][left] == '#') ?? ?{ ?? ??? ?game[up][left] = mine[up][left]; ?? ??? ?count++; ?? ?} ?? ?if (right < MAX_COL && game[up][right] == '#' && mine[up][right] == '0') ?? ?{ ?? ??? ?game[up][right] = '0'; ?? ??? ?count = count + continues_map(game, mine, up, right); ?? ?} ?? ?else if (right < MAX_COL && game[up][right] == '#') ?? ?{ ?? ??? ?game[up][right] = mine[up][right]; ?? ??? ?count++; ?? ?} ?? ?if (left >= 0 && game[down][left] == '#' && mine[down][left] == '0') ?? ?{ ?? ??? ?game[down][left] = '0'; ?? ??? ?count = count + continues_map(game, mine, down, left); ?? ?} ?? ?else if (left >= 0 && game[down][left] == '#') ?? ?{ ?? ??? ?game[down][left] = mine[down][left]; ?? ??? ?count++; ?? ?} ?? ?if (right < MAX_COL && game[down][right] == '#' && mine[down][right] == '0') ?? ?{ ?? ??? ?game[down][right] = '0'; ?? ??? ?count = count + continues_map(game, mine, down, right); ?? ?} ?? ?else if (right < MAX_COL && game[down][right] == '#') ?? ?{ ?? ??? ?game[down][right] = mine[down][right]; ?? ??? ?count++; ?? ?} ?? ?return count; }
一共八種情況和終止條件,寫成遞歸都挺復(fù)雜的,更不用說循環(huán)了······
寫到這里,一個(gè)用以實(shí)現(xiàn)掃雷游戲功能的程序大致就寫得差不多了,再加上一個(gè)主函數(shù)就可以玩了。主函數(shù)的功能主要就是對以上的函數(shù)進(jìn)行調(diào)用來實(shí)現(xiàn)游戲功能,并對玩家輸入的合法性進(jìn)行判斷,代碼如下:
int main() { ?? ?srand((unsigned)time(0)); ?? ?char mine_map[MAX_ROW][MAX_COL]; ?? ?char game_map[MAX_ROW][MAX_COL]; ?? ?int input_row = 0; ?? ?int input_col = 0; ?? ?int count = 0; ?? ?int increase = 0; ?? ?display_map(game_map); ?? ?while (count < MAX_ROW*MAX_COL - MAX_MINE) ?? ?{?? ??? ? ?? ??? ?//讀取坐標(biāo)并效驗(yàn) ?? ??? ?printf("輸入您將要翻開的格子的坐標(biāo)(row, col): \n"); ?? ??? ?scanf("%d %d", &input_row, &input_col); ?? ??? ?if (input_row<1 || input_row>MAX_ROW ?? ??? ??? ?|| input_col<1 || input_col>MAX_COL ?? ??? ??? ?|| game_map[input_row - 1][input_col - 1] != '#') ?? ??? ?{ ?? ??? ??? ?printf("輸入有誤,請重新輸入...\n"); ?? ??? ??? ?continue; ?? ??? ?} ?? ??? ?input_row -= 1; ?? ??? ?input_col -= 1; ?? ??? ?increase = renew_map(game_map, mine_map, input_row, input_col); ?? ??? ?display_map(game_map); ?? ??? ?if (increase == 0) ?? ??? ??? ?break; ?? ??? ?count = count + increase; ?? ?} ?? ?if (count == 0) ?? ??? ?printf("你是來自非洲的黑人朋友?\n"); ?? ?else if (count == MAX_ROW * MAX_COL - MAX_MINE) ?? ??? ?printf("YOU WIN!\n"); ?? ?else ?? ??? ?printf("游戲結(jié)束!\n"); ?? ?system("pause"); ?? ?return 0; }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C語言數(shù)據(jù)結(jié)構(gòu)順序表中的增刪改(尾插尾刪)教程示例詳解
這篇文章主要為大家介紹了C語言數(shù)據(jù)結(jié)構(gòu)順序表中的增刪改教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02對比C語言中g(shù)etc()函數(shù)和ungetc()函數(shù)的使用
這篇文章主要介紹了對比C語言中g(shù)etc()函數(shù)和ungetc()函數(shù)的使用,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-08-08C語言數(shù)據(jù)結(jié)構(gòu)之動態(tài)分配實(shí)現(xiàn)串
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之動態(tài)分配實(shí)現(xiàn)串的相關(guān)資料,希望通過本文能幫助到大家,讓大家實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)中動態(tài)分配實(shí)現(xiàn)串的實(shí)例,需要的朋友可以參考下2017-10-10