2021-11-10-
作者:Nico
時間: 2021-11-10
網站地址:[]:https://github.com/sxfinn
摘要
C語言實作我們小時候玩過的掃雷游戲,最近看到了一些掃雷游戲的簡單實作,但是總有功能上的缺失,玩起來不那么的“原汁原味”,因此我增加了一些新功能:
- 確保玩家首次排雷一定不會炸死,
- 加入了計時器記錄結束時間,
- 擴展式排雷,展開周圍的非雷區,
總結
比較難的一點是擴展式排雷,使用用遞回函式處理相對方便,
目錄
- 2021-11-10-
- 摘要
- 總結
- 難點決議
- 探索八區
- 遞回展開
- 完整原始碼
- test.c
- game.h
- game.c
- 一點拓展
難點決議
探索八區
探索排雷位的周圍八個區域,

總歸情況就分三類,可探索的區域為8個,5個,3個,但這樣分類實在麻煩,所以我們可以選擇在創建雷盤的時候,將二維陣列的維度擴大一些,使其不用考慮多種情況,而只用考慮探索周圍八個雷區,
我們可以給外側再加一層,即給二維陣列行列分別加二,并且把外層全部設定為非雷區域,就可以解決這一問題,
遞回展開
展開周圍的非雷區

遞回程序:如果(x,y)位置周圍八區的雷數為0,則從八個區域展開,展開的位置的 x坐標是從x-1到x+1,而 y 的位置是從y-1到y+1的范圍中,因此嵌套兩重回圈,
進入條件:只有之前沒有展開過,且坐標在雷盤內的位置才進入遞回,
終止條件:如果探索的周圍八個位置有雷,則停止,并讓該位置顯示雷的數量,
探索八區代碼實作:
提醒:’*‘是未排雷的區域,’ ‘是代表已經展開過的區域,
//計算排查位置處周圍雷的個數
int calculate(char mine[ROWS][COLS],int x,int y)
{
//因為雷的位置放的是字符‘1’
// 加起來之后應該分別減去‘0’,才得到雷的個數
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 * '0';
}
//展開周圍都沒有雷的雷盤(擴展式排雷)
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//擴展函式
{ //利用calculate函式判斷周圍是否有雷
if (calculate(mine, x, y) == 0)
{//判斷周圍雷的個數,若為0,則需要展開
show[x][y] = ' ';//展開的位置都置為空格
int i = 0;
int j = 0;
//該位置可以拓展才檢查周圍8個位置是否能拓展
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函式
//再依次進入判斷周圍8個位置是能被展開還是不能
expand(mine, show, i, j);
}
}
}
}
else
{
show[x][y] = calculate(mine, x, y)+'0';
//不需要展開則顯示附近雷的個數
}
}
這兩個函式結合起來使用便可以達到擴展展開的效果,
使用效果:

可以看到在選擇(5,6)后一片非雷區被展開了,并且邊緣部分的雷個數被列印在了相應位置,
由于實在找不到什么好看的符號代替’ ‘,看著可能會有點難受😭,歡迎評論區給出建議!
完整原始碼
我將代碼分為了test.c、game.h、game.c三個部分,
test.c是游戲實作的主體框架,
game.h是所用到的頭檔案以及自定義函式宣告,
game.c是游戲的具體實作模塊,
除了上面的遞回有些難度外,其他的都比較易懂,不再單獨闡述,下面的原始碼中我給出了每一步的注釋,解釋的很清楚,相信各位一邊看代碼一邊想會有更多的識訓,
test.c
#include"game.h"
int main()
{
srand((unsigned)time(NULL));
int input = 0;
do
{
menu();//選單
printf("請輸入->(1 / 0)\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();//選擇1就進入game
break;
case 0://選擇0就退出
printf("退出游戲!\n");
break;
default:
printf("輸入錯誤,請重新輸入:\n");
break;
}
} while (input);
return 0;
}
game.h
#pragma once
#include<Windows.h>
#include<stdio.h>
//時間戳函式頭檔案
#include<time.h>
//rand、srand函式頭檔案
#include<stdlib.h>
#define ROW 9//雷的區域的行數
#define COL 9//雷的區域的列數
#define ROWS ROW+2//陣列的一維大小
#define COLS COL+2//陣列的二維大小
#define NUM 50//雷的個數
//進入游戲
void game();
//列印選單
void menu();
//計時器
void set_time();
//初始化陣列
void init_array(char array[ROWS][COLS], int rows, int cols, char symbol);
//布置雷
void lay_mines(char mine[ROWS][COLS], int row, int col);
//列印
void show_interface(char show[ROWS][COLS], int row, int col);
//排查雷
void mine_detection(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//計算雷的個數
int calculate(char mine[ROWS][COLS], int x, int y);
//保證第一次安全排雷
int one_safe(char mine[ROWS][COLS], int x, int y);
//擴展式排雷以及記錄周圍的雷數量
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
game.c
#include"game.h"
//游戲流程
void game()
{
system("cls");//清屏
//創建兩個陣列
char mine[ROWS][COLS] = { 0 };//布置雷的陣列
char show[ROWS][COLS] = { 0 };//游戲界面的顯示雷個數的陣列
//初始化兩個陣列
init_array(mine, ROWS, COLS, '0');//初始化為‘0’
init_array(show, ROWS, COLS, '*');//初始化為‘*’
//布置雷
lay_mines(mine, ROW, COL);
//列印出show陣列
show_interface(show, ROW, COL);
//排查雷
mine_detection(mine, show, ROW, COL);
set_time();
}
//選單函式
void menu()
{
printf("***************************\n");
printf("* ****** MENU ******* *\n");
printf("* ******1.play******* *\n");
printf("* ******0.exit******* *\n");
printf("* ******************* *\n");
printf("***************************\n");
}
//計時函式,
void set_time()
{
//列印出從程式運行到目前所用的時間
printf("本次用時:%u s\n", clock() / CLOCKS_PER_SEC);
}
//初始化陣列
void init_array(char array[ROWS][COLS], int rows, int cols, char symbol)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
array[i][j] = symbol;
}
}
}
//布置地雷
void lay_mines(char mine[ROWS][COLS],int row,int col)
{
int count = NUM;//NUM為雷的個數
while (count)//回圈條件,每次count-1
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//展示游戲界面
void show_interface(char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i <= col; i++)//列印出列號
{
printf("%d ", i);
}
printf("\n");
for (i = 0; i <= col; i++)
{
printf("—");
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d|", i);//列印出行號
for (j = 1; j <= col; j++)
{
printf("%c ", show[i][j]);
}
printf("\n");
}
for (i = 0; i <= col; i++)
{
printf("—");
}
printf("\n");
}
//排查雷
void mine_detection(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
int cnt = 0;//cnt為已排查出的雷的個數
int x = 0;
int y = 0;
while (cnt < ROW * COL - NUM)//回圈條件是還有雷沒有排查
{
int ret = 0;
//輸入要排查雷的坐標
printf("請輸入要排查的坐標:(X,Y)\n");
scanf("%d %d", &x, &y);
if (cnt == 0)//第一次排雷
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//首次輸入的坐標如果是雷,則將雷移動到另一個位置
one_safe(mine, x, y);//保證第一次總不是雷
//因為無論如何都不是雷,直接跳轉到記錄雷和擴展的函式expand
goto next;
}
else
{
printf("坐標非法,請重新輸入:\n");
continue;
}
}
//確保坐標在設定的范圍中
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')
{
next://goto跳轉到這里
expand(mine, show, x, y);
//列印出排查過一次后的界面
show_interface(show, ROW, COL);
cnt++;//排雷成功次數加一
}
else
{
//如果排查的位置放的是‘1’則表示該位置為炸彈
printf("很遺憾,您被炸死了!\n");
//游戲結束,列印出所有雷的位置
show_interface(mine, ROW, COL);
break;
}
}
else
{
printf("坐標非法,請重新輸入:\n");
}
}
if (cnt == ROW * COL - NUM)//可排雷個數為0時
{
printf("恭喜您,排雷成功!\n");
//游戲結束
show_interface(mine, ROW, COL);
}
}
//計算排查位置處周圍雷的個數
int calculate(char mine[ROWS][COLS],int x,int y)
{
//因為雷的位置放的是字符‘1’
// 加起來之后應該分別減去‘0’,才得到雷的個數
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 * '0';
}
//保證第一次不會踩到雷
int one_safe(char mine[ROWS][COLS], int x, int y)
{
int count = 1;
//判斷第一次是否是雷
if (mine[x][y] == '1')
{
mine[x][y] = '0';
//將雷隨機放入另一個沒有雷的位置
while (count)
{
x = rand() % ROW + 1;
y = rand() % COL + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
return 1;//是雷則回傳1
}
else
return 0;//不是雷則回傳0
//其實這里可以回傳值也可以不回傳,因為在我最開始寫代碼時,最開始的思路是:
//非雷 是雷這兩種情況分別進入不同的分支,不過后來又換了一種思路
}
//展開周圍都沒有雷的雷盤(擴展式排雷)
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//擴展函式,判斷是否需要遞回式展開,
{ //利用calculate函式判斷周圍是否有雷
if (calculate(mine, x, y) == 0)
{//判斷周圍雷的個數,若為0,則需要展開
show[x][y] = ' ';//展開的位置都置為空格
int i = 0;
int j = 0;
//該位置可以拓展才檢查周圍8個位置是否能拓展
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函式
//再依次進入判斷周圍8個位置是能被展開還是不能
expand(mine, show, i, j);
}
}
}
}
else
{
show[x][y] = calculate(mine, x, y)+'0';
//不需要展開則顯示附近雷的個數
}
}
一點拓展
*計時*
運用clock函式,該函式需要的頭檔案為 “time.h”
函式原型:clock_t clock(void);
功能:程式從啟動到函式呼叫占用CPU的時間
這個函式回傳從“開啟這個程式行程”到“程式中呼叫clock()函式”時之間的CPU時鐘計時單元(clock tick)數,在MSDN中稱之為掛鐘時間;若掛鐘時間不可取,則回傳-1,其中clock_t是用來保存時間的資料型別,
void set_time()//計時
{
printf("用時:%u 秒\n", clock() / CLOCKS_PER_SEC);
}
如果覺得游戲太簡單,可以通過改變ROW(行數)和COL(列數)的大小改變雷盤大小,增大NUM(雷的個數)增加游戲難度,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356196.html
標籤:其他
上一篇:用模塊化思維方式打出掃雷游戲
