目 录
一 扫雷游戏介绍
扫雷规则:在一个9×9(初级)、16×16(中级)、16×30(高级)或自定义大小的方块矩阵中随机布置一定量的地雷(初级10个,中级40个,高级99个),再由玩家逐个翻开方块,如果翻开的方块不是地雷且其周围没有地雷,则继续翻开该方块周围八个非雷方块,直到翻开方块的周围有地雷,并在方块上显示周围八个区域的地雷数,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
二 游戏功能函数分析
1 菜单显示函数
简单的小游戏也需要一个简单的菜单界面,这个界面一般用printf()函数调整输出自己喜欢的样式就可以了。以下是函数具体的代码实现:
//菜单显示
void Menu() {
printf("\n 扫 雷 \n\n\n");
printf(" 菜 单 \n\n");
printf("************ 1 游戏开始 ************\n");
printf("************ 0 退出游戏 ************\n");
}
2 菜单选择函数
既然有了菜单,那就针对菜单做一个进行选择的功能吧,可以看见菜单里目前就是1\0两种选择,那么在这个函数里我们要实现的主要两个点是:提醒玩家进行选择并进行返回;在返回玩家的选择之前确认其输入是1\0两个值之间的一个,如果不是,提醒玩家重新输入选择。以下是函数具体的代码实现:
//选择功能
int MenuSelect() {
int select = 0;
while (1) {
printf("请选择(1\\0):>");
scanf("%d", &select);
getchar(); //清理换行符'\n'
if ((select == 1) || (select == 0)) {
return select;
}
else {
printf("选择无效,请重新选择!\n");
}
}
}
3 选择确认函数
当玩家选择退出游戏时,这里为了避免玩家不小心选择错误的可能,设置了进行选择确认的函数,确认玩家是否真的要退出游戏,如果是选择Y,否选择N(不区分大小写)并返回选择值。以下是函数具体的代码实现:
//选择确认功能
char SelectConfirm() {
char select = 'Y';
while (1) {
printf("请选择(Y\\N):>");
scanf("%c", &select);
getchar(); //清理换行符'\n'
if ((select >= 'a') && (select <= 'z')) {
select = 'A' + (select - 'a');
}
if ((select == 'Y') || (select == 'N')) {
return select;
}
else {
printf("选择无效,请重新选择!\n");
}
}
}
以上两个函数也可通用与其他小游戏中。
4 游戏初始化函数
为了更好的对游戏进程进行显示,这里设置了两个二维数组,一个用于存储每次游戏的雷区布置情况,一个用于存储玩家的地雷排查情况。而考虑到每次排查除了需要对排查点是否为雷进行判断,还需要判断其周围八个位置的含雷数,为避免数组越界问题,设置两个数组的上下左右四个方位的比原定游戏区都多出一行(列),即初始数组比实际游戏区在每维的长度都加上2,这样在判断的时候,边界条件只设定在游戏区就不会发生数组越界问题了。这里设置的游戏区为9x9的方阵,则需要对相应的两个11x11的区域进行初始化,可以选择不同的符号对不同的数组进行初始化,以下是初始化函数具体的代码实现:
void AreaInitial(char board[ROWS][COLS], int rows, int cols, char init) {
int i = 0;
int j = 0;
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
board[i][j] = init;
}
}
}
5 地雷布置函数
将游戏区初始化完成后,接下来就需要在存储每次游戏雷区布置情况的数组中通过电脑随机生成坐标位置布置确定数量的地雷,以下是函数具体的代码实现:
//地雷布置
void MineSet(char mine[ROWS][COLS], int row, int col, char mineInit, char mineCh) {
int count = MINE_NUM;
int x = 0;
int y = 0;
while (count) {
x = rand() % row + 1;
y = rand() % col + 1;
if (mine[x][y] == mineInit) {
mine[x][y] = mineCh;
count--;
}
}
}
6 游戏显示函数
地雷布置完成后,按照游戏流程,我们需要显示出游戏界面,那么就可以编写一个游戏显示函数来实现,前面提到了我们设置了两个数组,而在游戏时,我们只需要显示一个数组作为提供给玩家的游戏界面,上面包含了玩家游戏信息,至于另一个存储地雷布置情况的数组,我们可以选择在游戏结束时再询问玩家是否需要显示。为了方便玩家观察坐标,在显示时添加上行号和列号。以下是函数具体的代码实现:
//游戏区显示
void View(char board[ROWS][COLS], int row, int col) {
int i = 0;
int j = 0;
//打印列号
printf("\t");
for (i = 0; i <= col; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++) {
printf("\t%d ", i); //打印行号
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
7 获取当前位置周围地雷数函数
有了游戏界面后,就可以通过确定坐标位置来排雷了,如果一个位置不是雷,那就需要将该位置的初始符号更改为该位置周围雷数进行显示,所谓周围指的是以该位置为中心的除该位置以外的3 x 3的区域,这里就可以编写一个计数函数来实现,具体代码如下:
//获取当前位置周围地雷数
int Get_MineNum(char mine[ROWS][COLS], int x, int y, char mineCh) {
//若雷区标记符不以1\0表示
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 (mine[i][j] == mineCh) {
count++;
}
}
}
若雷区标记符以1\0表示
//int count = 0;
//count = 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];
return count;
}
8 对确定的非雷且未被排查过的位置进行递归判断
在扫雷游戏中有一个功能:有时我们点击一个非雷位置时会一下显示一大块区域出来,这样的显示是因为在排雷时还对周围进行了递归判断,能进行递归判断的条件有三点:该位置没有被排查过;该位置不是雷;该位置周围的地雷数量为0。满足以上三个条件,则可以对该位置周围的8个未被排查过的位置进行递归判断,这里还要注意一个点是在对满足边界条件的周围8个位置继续进行判断时,还要确保这8个位置也满足边界条件,因为中心位置满足边界条件不代表周围也能满足,如果超出边界可不必进行递归的判断。以下是函数具体的代码实现:
//对确定的非雷位置进行扫雷的递归判断
void ClearMine_Expand(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col, int x, int y, char showInit, char mineCh) {
show[x][y] = Get_MineNum(mine, x, y, mineCh) + '0';
if (show[x][y] == '0') {
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] == showInit) && (i != x && j != y) &&
(x >= 1 && x <= row && y >= 1 && y <= col)) {
ClearMine_Expand(mine, show, row, col, i, j, showInit, mineCh);
}
}
}
}
}
9 计算当前未被排查的非雷位置数
判断游戏结束的标志有两个,一个是踩雷结束游戏,一个是排查出所有的地雷,当排查出所有地雷时,代表剩下的未被排查的位置都是地雷,那么可以计算当前未被排查的非雷位置数,当该数为0且还未踩雷则表示游戏结束并成功排查出所有地雷。因此可以编写计数函数如下:
//计算当前未被排查的非雷位置数
int GetUnmine(char show[ROWS][COLS], int row, int col, char showInit) {
int i = 0;
int j = 0;
int count = 0;
for (i = 1; i <= row; i++) {
for (j = 1; j <= col; j++) {
if (show[i][j] == showInit) {
count++;
}
}
}
return count - MINE_NUM; //MINE_NUM为地雷数
}
10 扫雷函数
按照游戏流程利用以上函数编写完整的扫雷过程函数,流程:玩家输入坐标-->坐标不合法则重新输入,坐标合法-->若坐标被排查过则重新输入,若未被排查过-->判断坐标是否是雷,若是则结束游戏,若不是,则调用递归判断函数进行扩展-->回到玩家输入坐标继续游戏,直到排查出所有地雷或踩雷。具体代码实现如下:
//扫雷
void ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col, char showInit, char mineCh) {
int x = 0;
int y = 0;
int i = 0;
int j = 0;
int getUnmine = 1;
while (getUnmine) {
printf("\n请输入想要排查的位置(行 列):>");
scanf("%d %d", &x, &y);
getchar(); //清理换行符'\n'
if (((x >= 1) && (x <= row)) && ((y >= 1) && (y <= col))) {
//判断该位置是否被排查过
if (show[x][y] == showInit) {
if (mine[x][y] == mineCh) {
printf("\n踩雷!游戏结束!\n");
break;
}
else {
ClearMine_Expand(mine, show, row, col, x, y, showInit, mineCh);
getUnmine = GetUnmine(show, row, col, showInit);
system("cls");
printf("\n(地雷数:%d)当前游戏状态:\n\n", MINE_NUM);
View(show, ROW, COL);
}
}
else {
printf("\n该处已排查过,请重新选择!\n");
}
}
else {
printf("\n输入不合法,请重新输入!\n");
}
}
if (getUnmine == 0) {
printf("\n游戏结束,恭喜排查出所有地雷!\n");
}
}
三 函数整合
将上述函数整合进game.c文件中,在相应的game.h头文件中对各个函数进行声明且对两个数组的大小和地雷的数量进行宏定义,同时在game.c文件中包含头文件game.h。具体实现如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//菜单显示
void Menu() {
printf("\n 扫 雷 \n\n\n");
printf(" 菜 单 \n\n");
printf("************ 1 游戏开始 ************\n");
printf("************ 0 退出游戏 ************\n");
}
//选择功能
int MenuSelect() {
int select = 0;
while (1) {
printf("请选择(1\\0):>");
scanf("%d", &select);
getchar(); //清理换行符'\n'
if ((select == 1) || (select == 0)) {
return select;
}
else {
printf("选择无效,请重新选择!\n");
}
}
}
//选择确认功能
char SelectConfirm() {
char select = 'Y';
while (1) {
printf("请选择(Y\\N):>");
scanf("%c", &select);
getchar(); //清理换行符'\n'
if ((select >= 'a') && (select <= 'z')) {
select = 'A' + (select - 'a');
}
if ((select == 'Y') || (select == 'N')) {
return select;
}
else {
printf("选择无效,请重新选择!\n");
}
}
}
//初始化
void AreaInitial(char board[ROWS][COLS], int rows, int cols, char init) {
int i = 0;
int j = 0;
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
board[i][j] = init;
}
}
}
//地雷布置
void MineSet(char mine[ROWS][COLS], int row, int col, char mineInit, char mineCh) {
int count = MINE_NUM;
int x = 0;
int y = 0;
while (count) {
x = rand() % row + 1;
y = rand() % col + 1;
if (mine[x][y] == mineInit) {
mine[x][y] = mineCh;
count--;
}
}
}
//游戏区显示
void View(char board[ROWS][COLS], int row, int col) {
int i = 0;
int j = 0;
//打印列号
printf("\t");
for (i = 0; i <= col; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++) {
printf("\t%d ", i); //打印行号
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
//获取当前位置周围地雷数
int Get_MineNum(char mine[ROWS][COLS], int x, int y, char mineCh) {
//若雷区标记符不以1\0表示
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 (mine[i][j] == mineCh) {
count++;
}
}
}
若雷区标记符以1\0表示
//int count = 0;
//count = 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];
return count;
}
//对确定的非雷位置进行扫雷的递归判断
void ClearMine_Expand(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col, int x, int y, char showInit, char mineCh) {
show[x][y] = Get_MineNum(mine, x, y, mineCh) + '0';
if (show[x][y] == '0') {
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] == showInit) && (i != x && j != y) &&
(x >= 1 && x <= row && y >= 1 && y <= col)) {
ClearMine_Expand(mine, show, row, col, i, j, showInit, mineCh);
}
}
}
}
}
//计算当前未被排查的非雷位置数
int GetUnmine(char show[ROWS][COLS], int row, int col, char showInit) {
int i = 0;
int j = 0;
int count = 0;
for (i = 1; i <= row; i++) {
for (j = 1; j <= col; j++) {
if (show[i][j] == showInit) {
count++;
}
}
}
return count - MINE_NUM;
}
//扫雷
void ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col, char showInit, char mineCh) {
int x = 0;
int y = 0;
int i = 0;
int j = 0;
int getUnmine = 1;
while (getUnmine) {
printf("\n请输入想要排查的位置(行 列):>");
scanf("%d %d", &x, &y);
getchar(); //清理换行符'\n'
if (((x >= 1) && (x <= row)) && ((y >= 1) && (y <= col))) {
//判断该位置是否被排查过
if (show[x][y] == showInit) {
if (mine[x][y] == mineCh) {
printf("\n踩雷!游戏结束!\n");
break;
}
else {
ClearMine_Expand(mine, show, row, col, x, y, showInit, mineCh);
getUnmine = GetUnmine(show, row, col, showInit);
system("cls");
printf("\n(地雷数:%d)当前游戏状态:\n\n", MINE_NUM);
View(show, ROW, COL);
}
}
else {
printf("\n该处已排查过,请重新选择!\n");
}
}
else {
printf("\n输入不合法,请重新输入!\n");
}
}
if (getUnmine == 0) {
printf("\n游戏结束,恭喜排查出所有地雷!\n");
}
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define MINE_NUM 10
//菜单显示
void Menu();
//选择功能
int MenuSelect();
//选择确认功能
char SelectConfirm();
//初始化
void AreaInitial(char board[ROWS][COLS], int rows, int cols, char init);
//地雷布置
void MineSet(char mine[ROWS][COLS], int row, int col, char mineInit, char mineCh);
//游戏区显示
void View(char board[ROWS][COLS], int row, int col);
//获取当前位置周围地雷数
int Get_MineNum(char mine[ROWS][COLS], int x, int y, char mineCh);
//对确定的非雷位置进行扫雷的递归判断
void ClearMine_Expand(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col, int x, int y, char showInit, char mineCh);
//计算当前未被排查的非雷位置数
int GetUnmine(char show[ROWS][COLS], int row, int col, char showInit);
//扫雷
void ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col, char showInit, char mineInit);
四 主函数(扫雷游戏的实现)
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void game() {
//设置两个数组用于存放信息
char mine[ROWS][COLS] = { 0 }; //存放地雷布置信息
char show[ROWS][COLS] = { 0 }; //存放扫雷信息
char mineInit = ' ';
char showInit = '*';
char mineCh = '!';
AreaInitial(mine, ROWS, COLS, mineInit); //雷区初始化
AreaInitial(show, ROWS, COLS, showInit); //显示区初始化
MineSet(mine, ROW, COL, mineInit, mineCh); //布置地雷,以'!'表示
printf("\n(地雷数:%d)当前游戏状态:\n\n", MINE_NUM);
View(show, ROW, COL);
ClearMine(mine, show, ROW, COL, showInit, mineCh); //扫雷
printf("\n是否显示原始地雷布置情况,");
int selectConfirm = SelectConfirm();
if (selectConfirm == 'Y') {
printf("\n原始地雷布置情况:\n\n");
View(mine, ROW, COL);
}
}
int main() {
srand((unsigned int)time(NULL));
int select = 0;
int selectConfirm = 0;
do{
system("cls");
Menu();
select = MenuSelect();
switch (select) {
case 1:
system("cls");
printf("\n游戏开始!\n");
//Sleep(800);
game();
printf("\n返回菜单或直接退出游戏,");
select = MenuSelect();
if (select == 0) {
printf("\n确认是否退出游戏,");
selectConfirm = SelectConfirm();
if (selectConfirm == 'Y') {
printf("\n退出游戏!\n");
}
else {
select = 1;
}
}
break;
case 0:
printf("\n确认是否退出游戏,");
selectConfirm = SelectConfirm();
if (selectConfirm == 'Y') {
printf("\n退出游戏!\n");
}
else {
select = 1;
}
break;
default:
;
}
} while (select);
return 0;
}
五 游戏过程截图