柚子快報邀請碼778899分享:c語言 掃雷(C 語言)
柚子快報邀請碼778899分享:c語言 掃雷(C 語言)
目錄
一、游戲設計分析二、各個步驟的代碼實現(xiàn)1. 游戲菜單界面的實現(xiàn)2. 游戲初始化3. 開始掃雷
三、完整代碼四、總結(jié)
一、游戲設計分析
本次設計的掃雷游戲是展示一個 9 * 9 的棋盤,然后輸入坐標進行判斷,若是雷,則游戲結(jié)束,否則繼續(xù)判斷,直到排除所有雷,游戲勝利。然后選擇退出或者重新開始游戲。
(1)游戲菜單界面實現(xiàn) 提供開始游戲和退出游戲兩個選項,游戲結(jié)束后可以再次選擇,直到退出游戲。 (2)游戲初始化 開始游戲后,初始化棋盤,隨機設置地雷,然后顯示棋盤。這里需要兩個二維數(shù)組,一個用來顯示棋盤,一個用來存儲地雷的信息。 (3)開始掃雷 提示玩家輸入坐標,然后檢查坐標是否合法,最后判斷位置是否為雷。若為雷,則游戲結(jié)束;若不為雷則計算周圍地雷的個數(shù),若周圍地雷的個數(shù)不為 0,則顯示將其顯示在棋盤上;若周圍地雷個數(shù)為 0,則使用剛才的方法繼續(xù)檢查周圍的 8 個位置,以此類推,檢查結(jié)束后把信息全部顯示在棋盤上。然后繼續(xù)下一次坐標輸入,直到把所有的雷找出,游戲獲勝。
上述需要計算輸入坐標周圍的的雷的個數(shù),如果在該坐標在最邊上一圈,那么會存在排查的坐標非法的情況。這里可以使用 11 * 11 的棋盤,然后通過把雷設置為字符 ‘1’,非雷設置為字符 ‘0’,當計算時把周圍八個坐標相加然后減去 8 個字符 ‘0’ 即可。 (4)游戲結(jié)束或重新開始游戲
二、各個步驟的代碼實現(xiàn)
1. 游戲菜單界面的實現(xiàn)
游戲菜單界面需要提供開始游戲和退出游戲兩個選項,且玩完一局游戲之后可以選擇再玩一局。在主函數(shù)使用一個循環(huán)來控制實現(xiàn),如下:
(1)test.c 測試文件
// 頭文件
#include "Mine.h"
int main()
{
// 所需變量
int select;
// 選擇
do
{
// 菜單
menu();
// 輸入
scanf("%d", &select);
// 判斷
switch (select)
{
case 1:
printf("掃雷游戲\n");
//Minesweeper_game();
break;
case 0:
printf("游戲結(jié)束!\n");
break;
default:
printf("輸入錯誤,請重新輸入!\n");
break;
}
} while (select);
return 0;
}
(2)Mine.h 頭文件
// 頭文件
#include
// 函數(shù)聲明
// 菜單
void menu();
(3)Mine.c 函數(shù)實現(xiàn)文件
// 頭文件
#include "Mine.h"
// 函數(shù)定義
// 菜單
void menu()
{
printf("*****************************************\n");
printf("************* 1. play *************\n");
printf("************* 0. exit *************\n");
printf("*****************************************\n");
}
(4)代碼運行效果:
2. 游戲初始化
游戲初始化需要顯示棋盤,然后隨機設置雷。這里需要使用兩個數(shù)組,一個數(shù)組用來顯示棋盤,另一個數(shù)組用來存儲雷的信息。為了方便計算,兩個數(shù)組都采用 11*11 的字符數(shù)組,初始棋盤使用符號 ‘*’,地雷使用字符 ‘1’,非地雷使用字符 ‘0’。數(shù)組大小使用在頭文件中定義的符號常量,設計一個初始化函數(shù) InitBoard() 和設置雷函數(shù) SetMine(),還有顯示棋盤函數(shù) PrintBoard()。
InitBoard() 函數(shù)可以一次性傳兩個數(shù)組,也可以通過參數(shù)傳遞設置的值。SetMine() 函數(shù)需要使用隨機數(shù)。PrintBoard() 函數(shù)顯示的時候可以在左邊和上面加上行號和列號,方便玩家輸入坐標。
(1)test.c 測試文件 添加了掃雷游戲函數(shù) Minesweeper_game()
// 頭文件
#include "Mine.h"
int main()
{
// 所需變量
int select;
// 選擇
do
{
// 菜單
menu();
// 輸入
scanf("%d", &select);
// 判斷
switch (select)
{
case 1:
Minesweeper_game(); // 掃雷游戲
break;
case 0:
printf("游戲結(jié)束!\n");
break;
default:
printf("輸入錯誤,請重新輸入!\n");
break;
}
} while (select);
return 0;
}
(2)Mine.h 頭文件
// 頭文件
#include
// 常量聲明
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
// 函數(shù)聲明
// 菜單
void menu();
// 初始化棋盤
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set);
// 設置雷
void SetMine(char Board[ROWS][COLS], int row, int col);
// 顯示棋盤
void PrintBoard(char Board[ROWS][COLS], int row, int col);
// 掃雷游戲
void Minesweeper_game();
(3)Mine.c 函數(shù)實現(xiàn)文件
// 初始化棋盤
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set)
{
int i;
for (i = 0; i < rows; ++i)
{
int j;
for (j = 0; j < cols; ++j)
{
Board[i][j] = set;
}
}
}
// 設置雷
void SetMine(char Board[ROWS][COLS], int row, int col)
{
// 所需變量
int x, y; // 雷的坐標
int num = MINE_COUNT; // 雷的數(shù)量
// 設置雷
while (num)
{
// 隨機設置坐標值
x = rand() % row + 1; // 11 * 11 實際坐標 1 - 9
y = rand() % col + 1;
// 檢查坐標是否重復
if (Board[x][y] == '0')
{
Board[x][y] = '1';
--num;
}
}
}
// 顯示棋盤
void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
// 分界線
printf("-----------------------掃雷----------------------\n");
// 列號
printf(" ");
int j;
for (j = 0; j < col; ++j)
{
printf("%d ", j + 1);
}
printf("\n");
// 棋盤
int i;
for (i = 0; i < row; ++i)
{
// 列號
printf("%d ", i + 1);
for (j = 0; j < col; ++j)
{
printf("%c ", Board[i+1][j+1]);
}
// 下一行
printf("\n");
}
// 分界線
printf("-----------------------掃雷----------------------\n");
}
// 掃雷游戲
void Minesweeper_game()
{
// 創(chuàng)建兩個棋盤
char board[ROWS][COLS];
char mine[ROWS][COLS];
// 初始化棋盤
InitBoard(board, ROWS, COLS, '*');
printf("初始化兩個數(shù)組:\n"); // 刪除
PrintBoard(board, ROW, COL); // 刪除
InitBoard(mine, ROWS, COLS, '0');
PrintBoard(mine, ROW, COL); // 刪除
// 設置雷
SetMine(mine, ROW, COL);
printf("雷的信息:\n"); // 刪除
PrintBoard(mine, ROW, COL); // 刪除
}
上述標記了刪除的語句都是不需要的,這是作者寫完代碼用來檢測的。
(4)代碼運行效果
3. 開始掃雷
開始掃雷,設計一個函數(shù) find(),要求玩家輸入坐標,檢查該坐標是否合法,然后判斷該位置是否為雷,是雷則游戲結(jié)束,不是雷則使用函數(shù) calc_mine() 計算周圍一圈的雷的個數(shù)。如果周圍一圈沒有雷,則按照剛才的步驟檢查周圍一圈,實現(xiàn)函數(shù) calc_mine() 遞歸。然后繼續(xù)掃雷,直到找出所有的雷。
函數(shù) calc_mine() 只在 find() 函數(shù)中使用,可以設置為 Mine.c 函數(shù)實現(xiàn)文件中的靜態(tài)函數(shù)。判斷游戲結(jié)束也需要使用一個變量,這里使用一個全局變量 REAMIN 來進行控制,當 REAMIN 的值為棋子總數(shù)減去雷數(shù)時,則排除所有雷,游戲結(jié)束。
代碼測試時,可以一個一個功能進行測試,一個功能完成無誤后再進入下一個。作者是全部測試好了才放上來的。
(1)Mine.h 頭文件
// ...
// 找雷
void find(char Board[ROWS][COLS], int row, int col);
// 掃雷游戲
void Minesweeper_game();
(2)Mine.c 函數(shù)實現(xiàn)文件
// 計算周圍的雷
void calc_mine(char Board[ROWS][COLS], char Mine[ROWS][COLS], int row, int col, int x, int y)
{
// 坐標非法或者已經(jīng)排查或該位置為雷則退出
if (x < 1 || x > 9 || y < 1 || y > 9 || Board[x][y] != '*' || Mine[x][y] != '0')
return;
// 所需變量
int num = 0;
int i;
for (i = x - 1; i <= x + 1; ++i)
{
int j;
for (j = y - 1; j <= y + 1; ++j)
{
num += Mine[i][j];
}
}
// 加的是字符 '0' 所以要減去
num = num - 9 * '0';
// 排雷 +1
++REMAIN;
if (num)
{
Board[x][y] = num + '0';
}
else
{
Board[x][y] = ' ';
// 如果該坐標周圍沒雷,則對其周圍八個坐標進行檢查,形成遞歸
for (i = x - 1; i <= x + 1; ++i)
{
int j;
for (j = y - 1; j <= y + 1; ++j)
{
calc_mine(Board, Mine, row, col, i, j);
}
}
}
}
// 找雷
int find(char Board[ROWS][COLS], char Mine[ROWS][COLS], int row, int col)
{
// 所需變量
int x, y;
// 輸入排查坐標
while (1)
{
printf("請輸入排雷坐標:");
scanf("%d %d", &x, &y);
// 判斷坐標是否合法
if (Board[x][y] != '*')
{
printf("該坐標已被排查!\n");
continue;
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (Mine[x][y] == '1')
{
printf("很遺憾,你被炸死了!\n");
printf("以下是本局雷的信息\n");
return 0;
}
else
{
calc_mine(Board, Mine, row, col, x, y);
return 1;
}
}
}
}
// 掃雷游戲
void Minesweeper_game()
{
// 重置
REMAIN = 0;
// 創(chuàng)建兩個棋盤
char board[ROWS][COLS];
char mine[ROWS][COLS];
// 初始化棋盤
InitBoard(board, ROWS, COLS, '*');
InitBoard(mine, ROWS, COLS, '0');
// 設置雷
SetMine(mine, ROW, COL);
int ret = 0;
while (REMAIN != ROW * COL - MINE_COUNT)
{
// 顯示棋盤
PrintBoard(board, ROW, COL);
// 排雷
ret = find(board, mine, ROW, COL);
// 如果是雷,則顯示雷的信息,游戲結(jié)束
if (!ret)
{
PrintBoard(mine, ROW, COL);
break;
}
}
// 判斷
if (REMAIN == ROW * COL - MINE_COUNT)\
{
printf("恭喜你排雷成功,游戲結(jié)束!\n");
printf("以下是本局雷的信息:\n");
PrintBoard(mine, ROW, COL);
}
}
(3)代碼運行效果: 下圖是完整代碼運行效果,一整局游戲的運行圖:
三、完整代碼
完整代碼分為三個文件:頭文件 Mine.h,測試文件 test.c,函數(shù)實現(xiàn)文件 Mine.c
頭文件 Mine.h
#pragma once
// 頭文件
#include
#include
// 常量聲明
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE_COUNT 10
// 函數(shù)聲明
// 菜單
void menu();
// 初始化棋盤
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set);
// 設置雷
void SetMine(char Board[ROWS][COLS], int row, int col);
// 顯示棋盤
void PrintBoard(char Board[ROWS][COLS], int row, int col);
// 找雷
int find(char Board[ROWS][COLS],char Mine[ROWS][COLS], int row, int col);
// 掃雷游戲
void Minesweeper_game();
測試文件 test.c
// 頭文件
#include "Mine.h"
#include
int main()
{
// 設置隨機數(shù)種子
srand((unsigned)time(0));
// 所需變量
int select;
// 選擇
do
{
// 菜單
menu();
// 輸入
scanf("%d", &select);
// 判斷
switch (select)
{
case 1:
Minesweeper_game(); // 掃雷游戲
break;
case 0:
printf("游戲結(jié)束!\n");
break;
default:
printf("輸入錯誤,請重新輸入!\n");
break;
}
} while (select);
return 0;
}
函數(shù)實現(xiàn)文件 Mine.c
// 頭文件
#include "Mine.h"
// 全局靜態(tài)變量
static REMAIN = 0;
// 函數(shù)定義
// 菜單
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;
for (i = 0; i < rows; ++i)
{
int j;
for (j = 0; j < cols; ++j)
{
Board[i][j] = set;
}
}
}
// 設置雷
void SetMine(char Board[ROWS][COLS], int row, int col)
{
// 所需變量
int x, y; // 雷的坐標
int num = MINE_COUNT; // 雷的數(shù)量
// 設置雷
while (num)
{
// 隨機設置坐標值
x = rand() % row + 1; // 11 * 11 實際坐標 1 - 9
y = rand() % col + 1;
// 檢查坐標是否重復
if (Board[x][y] == '0')
{
Board[x][y] = '1';
--num;
}
}
}
// 顯示棋盤
void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
// 分界線
printf("-----------------------掃雷----------------------\n");
// 列號
printf(" ");
int j;
for (j = 0; j < col; ++j)
{
printf("%d ", j + 1);
}
printf("\n");
// 棋盤
int i;
for (i = 0; i < row; ++i)
{
// 列號
printf("%d ", i + 1);
for (j = 0; j < col; ++j)
{
printf("%c ", Board[i+1][j+1]);
}
// 下一行
printf("\n");
}
// 分界線
printf("-----------------------掃雷----------------------\n");
}
// 計算周圍的雷
void calc_mine(char Board[ROWS][COLS], char Mine[ROWS][COLS], int row, int col, int x, int y)
{
// 坐標非法或者已經(jīng)排查或該位置為雷則退出
if (x < 1 || x > 9 || y < 1 || y > 9 || Board[x][y] != '*' || Mine[x][y] != '0')
return;
// 所需變量
int num = 0;
int i;
for (i = x - 1; i <= x + 1; ++i)
{
int j;
for (j = y - 1; j <= y + 1; ++j)
{
num += Mine[i][j];
}
}
// 加的是字符 '0' 所以要減去
num = num - 9 * '0';
// 排雷 +1
++REMAIN;
if (num)
{
Board[x][y] = num + '0';
}
else
{
Board[x][y] = ' ';
// 如果該坐標周圍沒雷,則對其周圍八個坐標進行檢查,形成遞歸
for (i = x - 1; i <= x + 1; ++i)
{
int j;
for (j = y - 1; j <= y + 1; ++j)
{
calc_mine(Board, Mine, row, col, i, j);
}
}
}
}
// 找雷
int find(char Board[ROWS][COLS], char Mine[ROWS][COLS], int row, int col)
{
// 所需變量
int x, y;
// 輸入排查坐標
while (1)
{
printf("請輸入排雷坐標:");
scanf("%d %d", &x, &y);
// 判斷坐標是否合法
if (Board[x][y] != '*')
{
printf("該坐標已被排查!\n");
continue;
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (Mine[x][y] == '1')
{
printf("很遺憾,你被炸死了!\n");
printf("以下是本局雷的信息\n");
return 0;
}
else
{
calc_mine(Board, Mine, row, col, x, y);
return 1;
}
}
}
}
// 掃雷游戲
void Minesweeper_game()
{
// 重置
REMAIN = 0;
// 創(chuàng)建兩個棋盤
char board[ROWS][COLS];
char mine[ROWS][COLS];
// 初始化棋盤
InitBoard(board, ROWS, COLS, '*');
InitBoard(mine, ROWS, COLS, '0');
// 設置雷
SetMine(mine, ROW, COL);
int ret = 0;
while (REMAIN != ROW * COL - MINE_COUNT)
{
// 顯示棋盤
PrintBoard(board, ROW, COL);
// 排雷
ret = find(board, mine, ROW, COL);
// 如果是雷,則顯示雷的信息,游戲結(jié)束
if (!ret)
{
PrintBoard(mine, ROW, COL);
break;
}
}
// 判斷
if (REMAIN == ROW * COL - MINE_COUNT)\
{
printf("恭喜你排雷成功,游戲結(jié)束!\n");
printf("以下是本局雷的信息:\n");
PrintBoard(mine, ROW, COL);
}
}
四、總結(jié)
本次掃雷游戲的代碼實現(xiàn),總體來說還比較可以?;竟δ芫鶎崿F(xiàn),游戲也能正常進行。本游戲還有許多可以提升的地方,比如:代碼的優(yōu)化,雷的標記,難度的選擇等等。
本代碼稍微難一點的地方在于排查坐標周圍地雷數(shù)的函數(shù) calc_mine() 的遞歸,需要明確何時需要遞歸,和遞歸的結(jié)束條件。
復習一下遞歸需要滿足兩個條件: (1)需要結(jié)束條件 (2)每次遞歸都更加靠近這個結(jié)束條件
柚子快報邀請碼778899分享:c語言 掃雷(C 語言)
精彩鏈接
本文內(nèi)容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。