文章目录
一.简单的思路分析
在9*9的矩阵中,随机布置10个(或自定义个)炸弹,由玩家输入坐标进行扫雷,如果该坐标没雷,并且该坐标八方皆没有雷,那么我们就对该坐标进行展开,并继续判断该坐标的八方是否有雷。若该坐标没雷,但八方有雷,我们就在棋盘上提示玩家该坐标附近有几颗雷。玩家可以对认为有雷的坐标进行标记,若标记满10个,则可以提示程序判断输赢,若标记的坐标都有雷,则胜利;若标记的坐标有不存在雷的,则失败;
二.代码实现
1.制作菜单
先制作一个菜单,供玩家进行游戏的选择
若玩家选择play则进入game()函数进行游戏
//菜单
void menu()
{
printf("**************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**************************\n");
}
void Test()
{
int input = 0;//选择
do
{
menu();
printf("请选择>\n");//提示玩家选择
scanf("%d",&input);
switch(input)
{
case 1:
game();//实现扫雷游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择有误,请重新选择\n");
break;
}
}while(input);
}
int main()
{
Test();//三子棋游戏测试
return 0;
}
2.初始化扫雷棋盘
我们需要创建两个二维数组来进行操作。一个(mine)用来存放雷,用0表示无雷,1表示雷,游戏过程中不展示给玩家;另一个(show)用来让玩家进行扫雷,初始时用*覆盖
创建两个数组时行和列都得是11,这方便我们后面在进行展开及判断周围雷的数目时的函数运算;
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
void BoardInit(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;
}
}
}
3.打印扫雷棋盘
我们创建的二维数组是11*11的,但是实际上我们需要展示给玩家的棋盘只有9 * 9;所以我们打印时需要省略掉最外围(上下左右各一行/列)的行和列
void BoardPrint(char board[ROWS][COLS],int row,int col)
{
int i = 0;
int j = 0;
printf("----------扫雷游戏-----------\n");
for(i=0;i<=row;i++)
{
printf("%d ",i);//提示玩家坐标列数
}
printf("\n");
for(i=1;i<=row;i++)
{
printf("%d ",i);//提示玩家坐标行数
for(j=1;j<=col;j++)
{
printf("%c ",board[i][j]);
}
printf("\n");//每隔一行就换行
}
printf("----------扫雷游戏-----------\n");
}
4.设置雷
雷的位置是随机的,所以我们需要用到rand(),srand(),time()函数。
设置的雷存放在mine数组中
设置雷的数量
#define EAZY_COUT 10
void BoardSet(char mine[ROWS][COLS],int row,int col)//只需要将雷存放在玩家所能看到的9*9棋盘中便可
{
int cout = EAZY_COUT;
while(cout)
{
int x = rand() % row + 1;//炸弹坐标从 1 -> 9
int y = rand() % col + 1;
if(mine[x][y] == '0')//避免在相同坐标下设置雷
{
mine[x][y] = '1';//用1表示炸弹
cout--;
}
}
}
5.玩家排雷 && 判断输赢
玩家输入排雷坐标,若该坐标有雷,则游戏结束;若该坐标无雷,则判断该坐标八方的雷的个数并将其输入到棋盘上提醒玩家;
如果没有被炸死,那么在排完一次雷后,我们就对是否胜利进行判断,如果排满除了有炸弹的坐标外,就算胜利
void BoardFind(char mine[ROWS][COLS],char show[ROWS][COLS])//排雷过程
{
int x = 0;
int y = 0;
int cout = 0;//已排查过的坐标个数
int win_ret = 0;//用来接收是否胜利的返回值
while(1)
{
printf("请输入要排雷的坐标\n");
scanf("%d %d",&x,&y);
if(x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if(mine[x][y] == '1')
{
printf("很遗憾你被炸死了\n");
break;
}
else
{
int ret = ShowThunder(mine,x,y) + '0';//将数字转变为对应的字符
show[x][y] = ret;
system("cls");//每扫一次雷进行一次清屏
cout++;//每次排雷让排查的坐标个数加1
win_ret = is_win(cout,ROW,COL);
}
}
else
{
printf("输入坐标无效,请重新输入\n");
}
if(win_ret)
{
printf("恭喜你,扫雷成功\n");
break;
}
}
}
int ShowThunder(char mine[ROWS][COLS],int x,int y)//检测八方的雷的个数
{
int i = 0;
int j = 0;
int sum = 0;
for(i=-1;i<=1;i++)
{
for(j=-1;j<=1;j++)
{
if(j == 0 && i == 0)
{
continue;
}
else
{
sum += mine[x+i][y+j];//将除原本坐标外的八个方向的字符对应的ASCII码加起来
}
}
}
return sum - 8 * '0';//'1' - '0' == 1,若有8个雷则返回8;返回存在的雷的个数
}
int is_win(int cout,int row,int col)
{
int ret = row * col;
if(cout == ret - EAZY_COUT)
{
return 1;
}
return 0;
}
6.初阶完整代码
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void game()
{
char mine[ROWS][COLS] = { 0 };//用来存放雷的 --> 0表示无雷,雷用1表示
char show[ROWS][COLS] = { 0 };//显示给玩家看的
BoardInit(mine, ROWS, COLS, '0');//全部初始化为0,表示无雷
BoardInit(show, ROWS, COLS, '*');
system("cls");
BoardPrint(show, ROW, COL);
BoardSet(mine, ROW, COL);
BoardPrint(mine, ROW, COL);
BoardFind(mine, show);
}
void menu()
{
printf("**************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**************************\n");
}
void Test()
{
int input = 0;//选择
do
{
menu();
printf("请选择>\n");//提示玩家选择
scanf("%d", &input);
switch (input)
{
case 1:
game();//实现扫雷游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择有误,请重新选择\n");
break;
}
} while (input);
}
int main()
{
srand((unsigned int)time(NULL));
Test();//三子棋游戏测试
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void BoardInit(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 BoardPrint(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("----------扫雷游戏-----------\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");//每隔一行就换行
}
printf("----------扫雷游戏-----------\n");
}
void BoardSet(char mine[ROWS][COLS], int row, int col)//只需要将雷存放在玩家所能看到的9*9棋盘中便可
{
int cout = EAZY_COUT;
while (cout)
{
int x = rand() % row + 1;//炸弹坐标从 1 -> 9
int y = rand() % col + 1;
if (mine[x][y] == '0')//避免在相同坐标下设雷
{
mine[x][y] = '1';//用1表示炸弹
cout--;
}
}
}
void BoardFind(char mine[ROWS][COLS], char show[ROWS][COLS])//排雷过程
{
int x = 0;
int y = 0;
int cout = 0;//已排查过的坐标个数
int win_ret = 0;//用来接收是否胜利的返回值
while (1)
{
printf("请输入要排雷的坐标\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (mine[x][y] == '1')
{
printf("很遗憾你被炸死了\n");
break;
}
else
{
int ret = ShowThunder(mine, x, y) + '0';//将数字转变为对应的字符
show[x][y] = ret;
system("cls");
BoardPrint(show, ROW, COL);
cout++;//每次排雷让排查的坐标个数加1
win_ret = is_win(cout, ROW, COL);
}
}
else
{
printf("输入坐标无效,请重新输入\n");
}
if (win_ret)
{
printf("恭喜你,扫雷成功\n");
break;
}
}
}
int ShowThunder(char mine[ROWS][COLS], int x, int y)//检测八方的雷的个数
{
int i = 0;
int j = 0;
int sum = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (j == 0 && i == 0)
{
continue;
}
else
{
sum += mine[x + i][y + j];//将除原本坐标外的八个方向的字符对应的ASCII码加起来
}
}
}
return sum - 8 * '0';//'1' - '0' == 1,若有8个雷则返回8;返回存在的雷的个数
}
int is_win(int cout, int row, int col)
{
int ret = row * col;
if (cout == ret - EAZY_COUT)
{
return 1;
}
return 0;
}
game.h
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9//实际显示的行数
#define COL 9//实际显示的列数
#define ROWS ROW+2
#define COLS COL+2
//四面多展开2行2列,方便数据判断
#define EAZY_COUT 1
//接口函数
void BoardInit(char board[ROWS][COLS], int rows, int cols, char set);//初始化
void BoardPrint(char board[ROWS][COLS], int row, int col);//打印
void BoardSet(char mine[ROWS][COLS], int row, int col);//只需要将雷存放在玩家所能看到的9*9棋盘中便可
int ShowThunder(char mine[ROWS][COLS], int x, int y);//检测八方的雷的个数
void BoardFind(char mine[ROWS][COLS], char show[ROWS][COLS]);//排雷过程
int is_win(int cout,int row,int col);//判断输赢
三. 可以改进的地方
在扫雷中,当要进行扫雷的坐标及其八方皆无雷时,我们就可以对其进行扩展,如下图所示
并且玩家也可以根据自己的判断来标记雷的位置,当玩家标记完10个位置后便可以按照意愿让程序判断是否扫雷成功。
接下来,我们将原本实现过的扫雷进行升级
四.扫雷进阶
1.坐标扩展
利用递归,先判断八方是否有雷,有雷则返回,无雷将该坐标置为空格,并且利用循环对八方坐标进行递归,如果该坐标已经被判断过则无需再判断,重复此操作。
void BoardFind_Open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x >= 0 && x <= ROWS && y >= 0 && y <= COLS)
{
int n = ShowThunder(mine, x, y) + '0';//该坐标八方的雷的个数
if (n != '0')
{
show[x][y] = n;
}
else if(show[x][y] != ' ')
{
int i = 0;
int j = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (i == 0 && j == 0)
{
continue;
}
else
{
show[x][y] = ' ';
BoardFind_Open(mine, show, x + i, y+j);
}
}
}
}
else
{
return;
}
}
}
实现效果:
2.标记坐标 && 取消标记
如果标记次数不为10,则玩家可以进行标记操作;用 # 对棋盘进行标记,如果标记坐标与雷的坐标相等,就让胜点加1。每进行一次标记,那么就让标记次数减1
如果标记次数不为0,则玩家可以进行取消标记的操作。取消标记后,将#还原为原本的*,如果取消标记的坐标与雷的坐标相等,那么我们让胜点减1让标记次数加1。每进行1次取消标记,那么就让标记次数加1;
当胜点等于雷的个数时,玩家进行胜负判断后就会获得胜利。如果胜点不为雷的个数,判断胜负后就会失败。
int BoardMark(char mine[ROWS][COLS],char show[ROWS][COLS], int cout)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你要标记的坐标\n");
scanf("%d %d", &x, &y);
if (show[x][y] == '*')
{
show[x][y] = '#';//顺利进行标记
if (mine[x][y] == '1')//该坐标有雷
{
return 1;//让胜点加1
}
return 0;
}
else
{
if (show[x][y] == '#')
{
printf("该坐标已被标记\n");
}
else//该坐标附近有雷或者已经被扩展
{
printf("该坐标无法标记\n");
}
}
}
}
int BoardUnMark(char mine[ROWS][COLS], char show[ROWS][COLS], int cout)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你要取消标记的坐标\n");
scanf("%d %d", &x, &y);
if (show[x][y] == '#')
{
show[x][y] = '*';
if (mine[x][y] == '1')
{
return -1;
}
return 0;
}
else
{
printf("该坐标未进行过标记\n");
}
}
}
3.判断输赢
int is_win(int cout)
{
if (cout == EAZY_COUT)//如果标记的坐标都与雷的坐标相等
{
return 1;
}
else
{
return 0;
}
}
4.扫雷功能汇总(扫雷,标记,取消标记,判断输赢)
void BoardFunction(char mine[ROWS][COLS], char show[ROWS][COLS])
{
int option = 0;//选择进行何种功能
int cout = EAZY_COUT;//可标记的坐标数(与雷数相等)
int over = 0;//判断是否被炸死
int win_cout = 0;//标记坐标与炸弹坐标相等的个数
int ret = 0;//判断是否胜利
do
{
printf("1.扫雷\n");
printf("2.标记\n");
printf("3.取消标记\n");
printf("0.判断输赢\n");
printf("请选择:>\n");
scanf("%d", &option);
switch (option)
{
case 1:
over = BoardFind(mine, show);
break;
case 2:
{
if (cout != 0)
{
win_cout += BoardMark(mine, show, cout);
//如果标记的坐标与雷的坐标相等,则让相等的个数加1
//如果不相等,则加0
cout--;//执行完毕让标记次数减1
}
else
{
printf("你的标记次数已用完\n");
}
break;
}
case 3:
{
if (cout != EAZY_COUT)
{
win_cout += BoardUnMark(mine, show, cout);
//如果取消的标记坐标与雷的坐标相等,让其相等的个数减1
//如果不相等,则减0
cout++;//执行完毕让标记次数加1
break;
}
else
{
printf("你未进行过标记\n");
}
}
case 0:
{
if (cout != 0)
{
printf("你还有标记未使用\n");
option = 1;
}
else
{
ret = is_win(win_cout);
}
break;
}
default:
printf("选择无效,请重新输入\n");
break;
}
if (over)
{
break;
}
system("cls");
BoardPrint(show, ROW, COL);
} while (option);
if (over)
{
printf("很遗憾你被炸死了\n");
BoardPrint(mine, ROW, COL);
}
if (cout == 0)
{
if (ret)
{
printf("恭喜你扫雷成功\n");
BoardPrint(mine, ROW, COL);
}
else
{
printf("很遗憾扫雷失败\n");
BoardPrint(mine, ROW, COL);
}
}
}
五.进阶完整代码
game.h
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9//实际显示的行数
#define COL 9//实际显示的列数
#define ROWS ROW+2
#define COLS COL+2
//四面多展开2行2列,方便数据判断
#define EAZY_COUT 1
//接口函数
void BoardInit(char board[ROWS][COLS], int rows, int cols, char set);//初始化
void BoardPrint(char board[ROWS][COLS], int row, int col);//打印
void BoardSet(char mine[ROWS][COLS], int row, int col);//只需要将雷存放在玩家所能看到的9*9棋盘中便可
int ShowThunder(char mine[ROWS][COLS], int x, int y);//检测八方的雷的个数
int BoardFind(char mine[ROWS][COLS], char show[ROWS][COLS]);//排雷过程
void BoardFunction(char mine[ROWS][COLS], char show[ROWS][COLS]);//实现扫雷功能汇总
void BoardFind_Open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);//展开
int BoardMark(char mine[ROWS][COLS], char show[ROWS][COLS], int cout);//标记
int BoardUnMark(char mine[ROWS][COLS], char show[ROWS][COLS], int cout);//取消标记
int is_win(int cout);//判断输赢
game.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void BoardInit(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 BoardPrint(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("----------扫雷游戏-----------\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");//每隔一行就换行
}
printf("----------扫雷游戏-----------\n");
}
void BoardSet(char mine[ROWS][COLS], int row, int col)//只需要将雷存放在玩家所能看到的9*9棋盘中便可
{
int cout = EAZY_COUT;
while (cout)
{
int x = rand() % row + 1;//炸弹坐标从 1 -> 9
int y = rand() % col + 1;
if (mine[x][y] == '0')//避免在相同坐标下设雷
{
mine[x][y] = '1';//用1表示炸弹
cout--;
}
}
}
void BoardFunction(char mine[ROWS][COLS], char show[ROWS][COLS])
{
int option = 0;;
int cout = EAZY_COUT;
int over = 0;
int win_cout = 0;
int ret = 0;
do
{
printf("1.扫雷\n");
printf("2.标记\n");
printf("3.取消标记\n");
printf("0.判断输赢\n");
printf("请选择:>\n");
scanf("%d", &option);
switch (option)
{
case 1:
over = BoardFind(mine, show);
break;
case 2:
{
if (cout != 0)
{
win_cout += BoardMark(mine, show, cout);
cout--;
}
else
{
printf("你的标记次数已用完\n");
}
break;
}
case 3:
{
if (cout != EAZY_COUT)
{
win_cout += BoardUnMark(mine, show, cout);
cout++;
break;
}
else
{
printf("你未进行过标记\n");
}
}
case 0:
{
if (cout != 0)
{
printf("你还有标记未使用\n");
option = 1;
}
else
{
ret = is_win(win_cout);
}
break;
}
default:
printf("选择无效,请重新输入\n");
break;
}
if (over)
{
break;
}
system("cls");
BoardPrint(show, ROW, COL);
} while (option);
if (over)
{
printf("很遗憾你被炸死了\n");
BoardPrint(mine, ROW, COL);
}
if (cout == 0)
{
if (ret)
{
printf("恭喜你扫雷成功\n");
BoardPrint(mine, ROW, COL);
}
else
{
printf("很遗憾扫雷失败\n");
BoardPrint(mine, ROW, COL);
}
}
}
int BoardFind(char mine[ROWS][COLS], char show[ROWS][COLS])//排雷过程
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入要排雷的坐标\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (mine[x][y] == '1')
{
return 1;
}
else
{
BoardFind_Open(mine, show, x, y);
BoardPrint(show, ROW, COL);
return 0;
}
}
}
}
int ShowThunder(char mine[ROWS][COLS], int x, int y)//检测八方的雷的个数
{
int i = 0;
int j = 0;
int sum = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (j == 0 && i == 0)
{
continue;
}
else
{
sum += mine[x + i][y + j];//将除原本坐标外的八个方向的字符对应的ASCII码加起来
}
}
}
return sum - 8 * '0';//'1' - '0' == 1,若有8个雷则返回8;返回存在的雷的个数
}
void BoardFind_Open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x >= 0 && x <= ROWS && y >= 0 && y <= COLS)
{
int n = ShowThunder(mine, x, y) + '0';//该坐标八方的雷的个数
if (n != '0')
{
show[x][y] = n;
}
else if(show[x][y] != ' ')
{
int i = 0;
int j = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (i == 0 && j == 0)
{
continue;
}
else
{
show[x][y] = ' ';
BoardFind_Open(mine, show, x + i, y+j);
}
}
}
}
else
{
return;
}
}
}
int BoardMark(char mine[ROWS][COLS],char show[ROWS][COLS], int cout)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你要标记的坐标\n");
scanf("%d %d", &x, &y);
if (show[x][y] == '*')
{
show[x][y] = '#';
if (mine[x][y] == '1')
{
return 1;
}
return 0;
}
else
{
if (show[x][y] == '#')
{
printf("该坐标已被标记\n");
}
else
{
printf("该坐标无法标记\n");
}
}
}
}
int BoardUnMark(char mine[ROWS][COLS], char show[ROWS][COLS], int cout)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你要取消标记的坐标\n");
scanf("%d %d", &x, &y);
if (show[x][y] == '#')
{
show[x][y] = '*';
if (mine[x][y] == '1')
{
return -1;
}
return 0;
}
else
{
printf("该坐标未进行过标记\n");
}
}
}
int is_win(int cout)
{
if (cout == EAZY_COUT)
{
return 1;
}
else
{
return 0;
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void game()
{
char mine[ROWS][COLS] = { 0 };//用来存放雷的 --> 0表示无雷,雷用1表示
char show[ROWS][COLS] = { 0 };//显示给玩家看的
BoardInit(mine, ROWS, COLS, '0');//全部初始化为0,表示无雷
BoardInit(show, ROWS, COLS, '*');
BoardPrint(show, ROW, COL);
BoardSet(mine, ROW, COL);
BoardFunction(mine,show);
}
void menu()
{
printf("**************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**************************\n");
}
void Test()
{
int input = 0;//选择
do
{
menu();
printf("请选择>\n");//提示玩家选择
scanf("%d", &input);
switch (input)
{
case 1:
game();//实现扫雷游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择有误,请重新选择\n");
break;
}
} while (input);
}
int main()
{
srand((unsigned int)time(NULL));
Test();//三子棋游戏测试
return 0;
}