我相信在做的各位都是玩過掃雷的,游戲玩法就不贅述了,
直接進入主題:先思考后敲代碼!!
首先,我將掃雷分為兩個棋盤,一個放雷,另一個為玩家猜測盤,
這就有同學問了,設定一個棋盤不就完了,這樣不就搞復雜了嗎?
先簡短的回答這位同學的問題:
因為我的考慮是這樣的,我用‘1’代表有雷,‘0’代表沒有雷,如果放在一個盤中,出現多個1的時候,無法確定這是雷還是代表周圍8個格子中有一個雷,
進一步解釋
圖片參考: 
當雷是1,還有記錄數字也為1時,以下黃色標記位置為例:

此時點擊黃色位置,那么它顯示的數字是2而不是1,我們會發現雷‘’變多了‘’,
但又有同學要問了,為啥非要用1代表雷,0代表沒有雷呢,我用¥代表雷,@代表非雷就不會出現這種情況了,
其實這樣安排是沒問題的,我也鼓勵大家去嘗試一下,但分雙棋盤帶來的好處,經過后面的代碼分析會體現出來,(之后可以出一個棋盤的版本)
需要注意的是我們采用‘1’和‘0’,即字符1和字符0代表有雷和沒有雷,為什么要這樣安排呢?這利于我們后續設計陣列和函式,現在暫時解釋到這,后面會讓大家有一個更清楚,更系統的認知,
初步設計思路到此結束!現在開始發車了,請關好門窗,系好安全帶!!
首先我們分檔案設計游戲,分一個test.c來管理游戲的執行流程,一個game.c來實作游戲需要的自定義函式,一個game.h來封裝函式宣告,常數的定義,頭檔案的包含等,
test.c的初步游戲執行流程設計,參考以下代碼:
void menu()
{
printf("******************\n");
printf("*** 1.play ***\n");
printf("*** 0.exit ***\n");
printf("******************\n");
}
int main()
{
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;
}
因為游戲可能需要玩很多次,為了不重新打開游戲,此處采用回圈的形式,又因為游戲至少需要執行 一次,此處采用do while回圈,用while回圈其實問題也不大,好了,do while回圈首先執行的是列印選單,然后我們根據選單來選擇接下來要執行的分支,當我們選1的時候代表我們接下來要玩游戲,而選擇0的時候我們要退出游戲,在這里我們選擇用整形變數input來接受我們的選擇,用switch來執行我們的選擇,同時我們會發現安排退出數字為0時有一個好處,當input為0的時候直接可以退出回圈,而非0可以繼續回圈,這完美符合我們的選擇設計!!!
接下來就是函式game()的實作了,
我們剛剛討論了,我們需要兩個棋盤,一個雷盤,另一個為玩家猜測盤,
因此我們創建兩個二維陣列來代表這兩個盤,
即(雷盤) char mine [ ] [ ] = { 0 }; (玩家界面盤)char show [ ] [ ] = { 0 };但這兩個盤的大小分別為多大呢?這是我們要考慮的問題,根據初級掃雷難度,我們暫且安排為9X9的棋盤,為了更長遠的考慮,我們不可能直接這樣寫char mine [ 9 ] [ 9 ] = { 0 }; char show [ 9 ] [ 9 ] = { 0 };為了以后的維護和花小代價的控制難度,我們用#define分別定義ROW為9,COL為9.但是我們真的想的夠全面了嗎?如果要統計處于邊界位置周圍的8個雷怎么算呢?這樣無疑會造成陣列越界,因此我們需要將棋盤擴展成11X11的棋盤,因此我們這樣創建兩個棋盤char mine [ ROWS ] [ COLS ] = { 0 }; char mine [ ROWS ] [ COLS ] = { 0 };
這里需要簡單說一下:
#define ROWS ROW+2
#define COLS COL+2.
好了,我們終于有了可操作的物件了,現在我們需要給它們‘’洗個澡‘’,讓它更優雅,
先對其進行初始化,按照傳統取個函式名吧,initboard(),就是它了,上代碼!!!
void initboard(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;
}
}
}
在這里,我們用0初始化雷盤,代表所有位置都沒有雷,用 * 來保持對該位置的神秘感,用來玩家猜測,即 initboard(mine, ROWS, COLS, '0');
initboard(show, ROWS, COLS, '*');
雖然沒暫時沒完成列印函式,但并不妨礙我們欣賞初始化結果(參考下圖)


其實這里就可以體現為什么用字符‘1’和字符‘0’代表有雷和沒有雷了,而不是數字1和0了,因為我們只要一個函式就可以兩個盤都進行初始化,而如果一個棋盤是整數陣列,另一個是字符陣列,你就需要兩個初始化函式了,
初始化棋盤完了之后,我們需要完成剩下的列印函式,因為我們要檢查我們的初始化函式是否按照我們的想法實作了對棋盤的初始化,代碼如下:
void displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i <= col; i++)
{
if (i == 0)
{
printf(" %d ", i);
}
else
{
printf("%d ", i);
}
}
printf("\n------------------------------------------------------------\n");
for (i = 1; i <= row; i++)
{
printf(" %d |", i);
for (j = 1; j <= col; j++)
{
printf(" %c |", board[i][j]);
}
printf("\n------------------------------------------------------------\n");
}
}
由于篇幅限制,列印函式這里就不多贅述了,看個人的喜好決定棋盤的樣子,
沖沖沖,接下來就是對雷盤進行設定雷了,上代碼!!!
void setmine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
此處EASY_COUNT是#define EASY_COUNT 9定義的常函式,意思是雷的簡單數量,此處用rand()庫函式對坐標X和Y分別設計亂數,%row+1和%col+1確保X和Y在 1~row 范圍內,需要注意的是在給mine[ x ] [ y ]賦值的時候,要確保mine[ x ] [ y ]的位置沒有埋雷,如果缺少上圖if條件判斷,這設定的雷可能變少,因為重復賦值,會覆寫上次的值,
哈哈哈,終于來激動人心的排雷環節了,此處應有掌聲!!!先看圖

首先我們需要輸入排查雷的坐標,因此我們創建兩個整形變數X和Y,在我們輸入X和Y的值時候,可能輸入的X和Y并不符合要求,此時該 if 陳述句登場了,當我們輸入的值符合要求的適合,我們先要判斷是否mine [ X ] [ Y ] == '1',如果條件成立,則可以宣布游戲結束了,如果mine [ X ] [ Y ] == '1',不成立,則我們需要對坐標(X,Y)位置賦值,要統計它周圍8個位置有多少個雷,統計坐標(X,Y)位置周圍8個位置雷的個數我們用 int get_mine_count();代碼如下:
void foundmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int iswin = 0;
while (iswin < row * col - EASY_COUNT)
{
printf("請選擇要排查的坐標:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遺憾,你被炸死了\n");
displayboard(mine, row, col);
break;
}
else
{
iswin++;
expand(mine, show, row, col, x, y);
displayboard(show, row, col);
}
}
else
{
printf("輸入坐標不合法,請重新輸入\n");
}
}
if (iswin == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
}
}
排雷程序是整個游戲的核心,需要玩家排查出所有的雷的位置或者踩到雷,然后游戲才能結束,所以排雷函式一定是一個回圈程序,這樣才能取到多次排雷的效果,如何判斷勝利?我們采取這樣的一種設計思想:排查出所有非雷的位置,其他位置自然是雷,這樣達到一個掃雷的效果,此處我采用整形變數 iswin 來當計數器,當輸入一個非雷的坐標位置的時候,iswin+=1,當 iswin等于ROW*COL-EASY_COUNT的時候,即可判斷游戲勝利,
重點來了,expand()函式的詳細決議
這里重申以下get_mine_count()函式的功能:計算輸入坐標(X,Y)位置周圍8個位置有多少個雷,
expand()是一個擴展函式,功能是:
當排查的坐標位置get_mine_count()!= 0時候,將該位置的值改為get_mine_count()的回傳值,
當排查的坐標位置周圍為0個雷的時候,把該位置置為空,并檢查周圍8個位置是否它的周圍也是0個雷,如果周圍坐標位置有滿足條件get_mine_count == 0 ,這將這個位置也置為空,如果周圍周圍的位置也滿足條件get_mine_count == 0,這也將該位置置為0,如果周圍的周圍的周圍也滿足條件et_mine_count == 0.........,
說人話就是:以你輸入的位置為起點,只要該位置get_mine_count == 0,就把它置空,同時把周圍滿足get_mine_count == 0也置空,同時也把周圍這個位置也看做起點,顯然滿足遞回思想,用遞回能夠很舒服的解決,
上代碼!!!
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
if (get_mine_count(mine, x, y)==0)
{
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] == '*' && i > 0 && i <= row && j > 0 && j <= col)
{
expand(mine, show, row, col , i, j);
}
}
}
}
else
{
show[x][y] = get_mine_count(mine, x, y) + '0';
}
}
唉,果然放不太下,只能麻煩看客滑動看了,
為了表示出輸入位置周圍8個位置,我們用 i,j 變數以for回圈的方式體現,即以下8個位置
mine[x - 1][y - 1] mine[x - 1][ y ] mine[x - 1][y + 1]
mine[ x ][y - 1] mine[x][y + 1]
mine[x + 1][y - 1] mine[x + 1][ y ] mine[x + 1][y + 1]
需要注意的是:當邊界 get_mine_count == 0 時不能將它周圍位置置空,因為如果把它周圍位置置空,計算get_mine_count會出問題,具體可看get_mine_count的實作原理,因此以上遞回實作需要一個解決這個問題的限制條件,具體可看上述代碼,
以下補充get_mine_count的實作原理:
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
+ mine[x][y - 1] + mine[x][y + 1]
+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * 48;
}
重申get_mine_count()函式的功能:計算輸入坐標(X,Y)位置周圍8個位置有多少個雷,
因為有雷我用的是字符 ‘1’設定的,因此將這8個位置的值相加起來,就能知道雷的個數了,但不碰巧的是這不是數字1,而是字符 ‘1’,因為它的ASCII的值為49,所有它實際代表的是數字49,但這并妨礙我們相加,因為字符 ‘1’==字符 ‘0’+1的,所以我們可以把它們相加后減去字符‘0’即可,因為字符 ‘0’實際代表的是數字48.(這里多說一下,當字符 ‘0’ 以字符形式列印時,它列印的結果是字符 ‘0’ ,顯示的是0,和數字0一樣,當它以整形列印時,它列印的結果是48.),這里也體現了用字符‘1’和字符‘0’代表有雷和沒有雷的好處,
分享整個原始碼
test.c:
#include "game.h"
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
initboard(mine, ROWS, COLS, '0');
initboard(show, ROWS, COLS, '*');
setmine(mine, ROW, COL);
displayboard(show, ROW, COL);
displayboard(mine, ROW, COL);
foundmine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
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;
}
game.c:
#include "game.h"
void menu()
{
printf("******************\n");
printf("*** 1.play ***\n");
printf("*** 0.exit ***\n");
printf("******************\n");
}
void initboard(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;
}
}
}
void displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i <= col; i++)
{
if (i == 0)
{
printf(" %d ", i);
}
else
{
printf("%d ", i);
}
}
printf("\n------------------------------------------------------------\n");
for (i = 1; i <= row; i++)
{
printf(" %d |", i);
for (j = 1; j <= col; j++)
{
printf(" %c |", board[i][j]);
}
printf("\n------------------------------------------------------------\n");
}
}
void setmine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
+ mine[x][y - 1] + mine[x][y + 1]
+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * 48;
}
void foundmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int iswin = 0;
while (iswin < row * col - EASY_COUNT)
{
printf("請選擇要排查的坐標:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遺憾,你被炸死了\n");
displayboard(mine, row, col);
break;
}
else
{
iswin++;
expand(mine, show, row, col, x, y);
displayboard(show, row, col);
}
}
else
{
printf("輸入坐標不合法,請重新輸入\n");
}
}
if (iswin == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
}
}
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
if (get_mine_count(mine, x, y)==0)
{
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] == '*' && i > 0 && i <= row && j > 0 && j <= col)
{
expand(mine, show, row, col , i, j);
}
}
}
}
else
{
show[x][y] = get_mine_count(mine, x, y) + '0';
}
}
game.h:
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void menu();
void initboard(char board[ROWS][COLS], int rows, int cols, char set);
void displayboard(char board[ROWS][COLS], int row, int col);
void setmine(char mine[ROWS][COLS], int row, int col);
void foundmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y);
好了,以上就是掃雷的簡單實作了,看到這里還不點個贊嗎,哈哈,以后會出更多的系列,比如C語言所有關鍵字的決議與應用,或者更多的游戲實作,這是我的第一篇真正意義上的博客,希望大家多多關照,
上述如有錯誤,還請各位看官不吝賜教,在下感激不盡,
望與大家共同進步!天道酬勤!!人生值得,未來可期!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356848.html
標籤:其他
上一篇:掃雷 第二次完成較復雜游戲感悟
下一篇:如何做一款C語言小游戲——三子棋
