扫雷,经典休闲游戏,大部分人都会在微机课上偷偷来上一两局,是我们当时一段美好的记忆,那今天就让我带领大家用C语言实现扫雷。
有了上次实现三子棋的经验,这次扫雷也是一样,项目分成三部分。
一.功能实现
1.打印开始菜单
和三子棋一样,我们首先需要一个开始菜单来决定我们是开始游戏还是退出游戏、
void is_start()
{
printf("**********************\n");
printf("***** 1:开始 *****\n");
printf("***** 0:退出 *****\n");
printf("**********************\n");
}
2.实现选择
实现选择和我们的三子棋一样,有不懂的可以去我的三子棋博客里面看看。
int i = 0;
do
{
is_start();
printf("请选择:");
scanf_s("%d", &i);
if (i == 1)
{
game();
printf("开始游戏\n");
}
else if (i == 0)
{
printf("退出游戏\n");
}
else
{
printf("选择错误,重新选择\n");
}
} while (i);
3.游戏实现
(1)游戏棋盘该如何埋雷并且让雷不被看见?
我们知道,在三子棋里面我们只需要一个棋盘,直接能看到里面的“棋子”是什么,但是我们扫雷是不能看到”雷”在哪,那我们是不是可以创建两个二维数组,一个用于给我们的玩家看,一个用于我们的埋“雷”。
//两个数组一个给玩家看,一个用来埋雷
char mine[ROWS][LINS];//雷
char board[ROWS][LINS];//玩家
这里用的ROWS和LINS与三子棋用法一致,不过我们扫雷的棋盘是不是有边角,但是在我们后面要对一圈进行排查的时候,我们排查边角就会造成数组越界,所以我们这个ROWS和LINS比我们真正的棋盘的行列都长出2,比如我们扫雷正常是9×9的,在这里我们的ROWS和LINS就是11。
#define ROWS 11
#define LINS 11
#define ROW 9
#define LIN 9
(2)初始化棋盘
对于扫雷来说,我们两个进行初始化的时候,一个玩家看到的棋盘和真正的棋盘最好符号是不一样的,这里我选择将我们看到的棋盘初始化成“*”,真正棋盘初始成“0”,注意此处是字符0,因为我们的数组是char类型的,当我们不带''时候,会将我们这个0默认成在ASCII码里面对应的字符,也就是NULL,那初始化也就是将每一行每一列都过一遍,将每一个元素都替换成我们想要替换的。
void init_board(char mb[ROWS][LINS], int row, int lin, char str)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < lin; j++)
{
mb[i][j] = str;
}
}
}
(3)打印棋盘
我们扫雷的棋盘就很简单了,但是因为是9×9,总不能每次扫雷输入坐标的时候都数一数,很麻烦,不方便,所以我们可以横着标一行数字,竖着标一行数字,都是从1~9,并且我们打印棋盘也打印下标1~9的,这样我们输入的坐标和坐标对应元素的下标就是一致的了。
void print_board(char mb[ROWS][LINS], int row, int lin)
{
int j = 0;
for (j = 0; j <=lin; j++)
{
printf("%d ",j);
}
printf("\n");
int i = 0;
for (i = 1; i <= row; i++)
{
printf("%d ", i);
j = 0;
for (j = 1; j <= lin; j++)
{
printf("%c ", mb[i][j]);
}
printf("\n");
}
}
(4)埋雷
埋雷与我们三子棋的电脑下棋优化之前是一样的,都是利用时间戳一直在变的特性,利用rand和srand生成随机数,然后用ROW和LIN进行取模,注意这里取模结果要+1,因为我们的的雷需要在下标1~9之间,如果不加这个1的话,那取模的范围就是0~8,同时我们还需要判断这个随机的下标有没有被埋过雷了,写完这些之后我们可以看看埋雷之后我们真正的棋盘上有没有埋雷。
void bury_mine(char mine[ROWS][LINS], int row, int lin)
{
int count = 0;
while (1)
{
int i = rand() % row + 1;
int j = rand() % lin + 1;
if (mine[i][j] == '0')
{
mine[i][j] = '1';
count++;
}
if (count == MINE)
{
break;
}
}
}
//srand
int i = 0;
srand((unsigned int)time(NULL));
do
{
is_start();
printf("请选择:");
scanf_s("%d", &i);
if (i == 1)
{
game();
printf("开始游戏\n");
}
else if (i == 0)
{
printf("退出游戏\n");
}
else
{
printf("选择错误,重新选择\n");
}
} while (i);
(5)判断所排雷坐标是否合法
我们埋雷之后就开始排雷了,那排雷之前,我们需要确定我们所输入的坐标是否合法,是否在我们的1~9范围之内,是否被排查过,合法与不合法都返回去一个值,在往外面写一个选择语句,如果不合法告诉我们,坐标不合法,然后我们重新输入一个坐标,再次判断,如果合法,那就进行下一步动作。
int brush_legal(char board[ROWS][LINS], int row, int lin)
{
if (row < 10 && row>0&& lin < 10 && lin>0&& board[row][lin] == '*')
{
return 1;
}
else
{
return 0;
}
}
//
int i = 0;
int j = 0;
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
//进行下一步动作;
}
else
{
printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}
(6)检查坐标是否为雷
我们坐标合法之后,我们需要判断坐标是否就是我们的“雷”,如果是雷直接游戏结束,如果不是雷才能进行下一步的动作,那这里就和我们上面判断坐标是否合法就一样了,也是外面一个选择语句,然后函数去返回一个值。
int is_or_no(char mine[ROWS][LINS], int row, int lin)
{
if (mine[row][lin] == '1')
{
return 0;
}
else
{
return 1;
}
}
//
int n = is_or_no(mine, i, j);
if (n == 1)
{
//下一步动作;
}
else
{
printf("你被炸死了,游戏结束\n");
break;
}
(7)检查周围雷的个数
如果我们没有踩到“雷”,那下一步就是检查我们选择坐标周围一圈里面雷的个数,只需要将一圈里面的元素都检查一下,是不是雷,如果不是雷,那计数器加一,最后将我们的计数器个数返回去。
int num_mine(char mine[ROWS][LINS], int row, int lin)
{
int count = 0;
int i = 0;
for (i = row + 1; i > row - 2; i--)
{
int j = 0;
for (j = lin - 1; j < lin + 2; j++)
{
if (mine[i][j] == '1')
{
count++;
}
}
}
return count;
}
(8)递归展开
展开其实不难,当我们周围没有雷的时候,也就是计数器为0,返回值也为0,那我们就在以周围着八个坐标为中心坐标开始展开,遇到雷停下来,然后将我们的计数器加上一个“0”,然后替换掉我们棋盘棋盘上本来的“*”,用于表示周围有多少雷,其他周围没有雷的地方拿“ ”代替。
void brush_mine(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin)
{
int count = num_mine(mine, row, lin);
if (row > 0 && row <= ROW && lin>0 && lin <= LIN)
{
if (count == 0)
{
board[row][lin] = ' ';
int i = 0;
for (i = row + 1; i > row - 2; i--)
{
int j = 0;
for (j = lin - 1; j < lin + 2; j++)
{
if (board[i][j] == '*')
{
brush_mine(board, mine, row, lin);
}
}
}
}
else
{
board[row][lin] = '0' + count;
}
}
}
(9)判断是否胜利
判断是否胜利的时候只需要我们将我们能看到的数组整个游历一遍,每遇到一个“*”计数器就加一,当游历完整个数组计数器等于十的时候那就证明我们赢了,如果没有那就接着排查。
int win_mine(char board[ROWS][LINS], int row, int lin)
{
int count = 0;
int i = 0;
for (i = 1; i < row - 1; i++)
{
int j = 0;
for (j = 1; j < lin - 1; j++)
{
if (board[i][j] == '*')
{
count++;
}
}
}
if (count == 10)
{
return 1;
}
else
{
return 0;
}
}
//
if (win == 1)
{
printf("排雷成功\n");
break;
}
(10)插旗
因为加了插旗的功能,所以在我们选择排查之前需要加一个选项,就是我们是要插旗还是排查,插旗的时候因为我们已经判断过坐标了,先将我们要插旗的地方换一个图标,表示插旗,然后检查一下插旗的中这个坐标是否是我们的雷,如果是,那就返回1,然后我们外面接收到之后,也加一,到我们插到十个雷之后,我们也胜利了。
int flag(char board[ROWS][LINS],char mine[ROWS][LINS], int row, int lin)
{
int count = 0;
board[row][lin] = "|>";
if (mine[row][lin] == '1')
{
return 1;
}
return 0;
}
//
int win = 0;
int i = 0;
int j = 0;
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
int n = flag(board, mine, i, j);
if (n == 1)
{
win++;
}
if (win == 10)
{
printf("排雷成功\n");
break;
}
}
else
{
printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}
二.程序模块调用
我们将功能都实现了,那我们应该按照上面顺序去调用?在打游戏之前肯定会先打印开始菜单,并且一定会打印以从,那这里我们就使用do while 循环,然后调用我们的开始菜单打印,打印完菜单选择是否开始游戏或者退出,进入游戏之后,我们首先需要的也只需要一次的就是我们的初始化棋盘,只有初始化棋盘之后才能去进行后面一系列操作,初始化结束,我们需要的是一个初始化好的棋盘打印出来,让我们去选择坐标,使用接下来是打印棋盘,打印棋盘完之后,就开始选择是排查还是插旗,不管选择这两个哪个,都会去判断坐标是否合法,这些动作包括之后的动作是需要一直重复去做的,所以这里我们将这些东西写进死循环里面,当我们胜利或者失败,跳出,那检查完坐标,并且坐标合法之后需要判断的就是我们这个坐标下面埋的是不是“雷”,如果不是,那就开始我们的找雷个数和递归展开,每次排查过或者插旗之后,我们都需要去检查一下我们现在是不是赢了,那大体思路就是只有,我们把他们穿起来就是:
//游戏实现模块
void game()
{
//两个数组一个给玩家看,一个用来埋雷
char mine[ROWS][LINS];//雷
char board[ROWS][LINS];//玩家
//初始化棋盘
init_board(mine, ROWS, LINS, '0');
init_board(board, ROWS, LINS, '*');
//打印棋盘
print_board(board, ROW, LIN);
//埋雷
bury_mine(mine, ROW, LIN);
//print_board(mine, ROWS, LINS);
//排查坐标是否合法
while (1)
{
int x = 0;
printf("选择排查(1)还是插旗(2):>");
scanf("%d", &x);
if (x == 1)
{
int win = 0;
int i = 0;
int j = 0;
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
//排雷
int n = is_or_no(mine, i, j);
if (n == 1)
{
brush_mine(board, mine, i, j);
print_board(board, ROW, LIN);
win = win_mine(board, ROWS, LINS);
if (win == 1)
{
printf("排雷成功\n");
break;
}
}
else
{
printf("你被炸死了,游戏结束\n");
break;
}
}
else
{
printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}
}
else if (x == 2)
{
int win = 0;
int i = 0;
int j = 0;
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
int n = flag(board, mine, i, j);
if (n == 1)
{
win++;
}
if (win == 10)
{
printf("排雷成功\n");
break;
}
}
else
{
printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}
}
else
{
printf("选择错误,重新选择\n");
}
}
}
源码:
game.h
#pragma once
//库函数的头文件
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
//#define定义的宏变量
#define ROWS 11
#define LINS 11
#define ROW 9
#define LIN 9
#define MINE 10
//为实现游戏功能所使用的自定义函数
void init_board(char mb[ROWS][LINS], int row, int lin, char str);//初始化棋盘
void print_board(char mb[ROWS][LINS], int row, int lin);//打印棋盘
void bury_mine(char mine[ROWS][LINS], int row, int lin);//埋雷
int brush_legal(char board[ROWS][LINS], int row, int lin);//判断排查坐标是是否合法
int is_or_no(char mine[ROWS][LINS], int row, int lin);//检查坐标是否为雷
int num_mine( char mine[ROWS][LINS], int row, int lin);//获取坐标周围雷的个数
void brush_mine(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin);//递归展开排雷
int win_mine(char board[ROWS][LINS], int row, int lin);//判断是否胜利
int flag(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin);//插旗
game.c
#include "game.h"
//初始化棋盘
void init_board(char mb[ROWS][LINS], int row, int lin, char str)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < lin; j++)
{
mb[i][j] = str;
}
}
}
//打印棋盘
void print_board(char mb[ROWS][LINS], int row, int lin)
{
int j = 0;
for (j = 0; j <=lin; j++)
{
printf("%d ",j);
}
printf("\n");
int i = 0;
for (i = 1; i <= row; i++)
{
printf("%d ", i);
j = 0;
for (j = 1; j <= lin; j++)
{
printf("%c ", mb[i][j]);
}
printf("\n");
}
}
//埋雷
void bury_mine(char mine[ROWS][LINS], int row, int lin)
{
int count = 0;
while (1)
{
int i = rand() % row + 1;
int j = rand() % lin + 1;
if (mine[i][j] == '0')
{
mine[i][j] = '1';
count++;
}
if (count == MINE)
{
break;
}
}
}
//判断排查坐标是否合法
int brush_legal(char board[ROWS][LINS], int row, int lin)
{
if (row < 10 && row>0&& lin < 10 && lin>0&& board[row][lin] == '*')
{
return 1;
}
else
{
return 0;
}
}
//检查所选坐标是否为雷
int is_or_no(char mine[ROWS][LINS], int row, int lin)
{
if (mine[row][lin] == '1')
{
return 0;
}
else
{
return 1;
}
}
//获取坐标周围雷的个数
int num_mine(char mine[ROWS][LINS], int row, int lin)
{
int count = 0;
int i = 0;
for (i = row + 1; i > row - 2; i--)
{
int j = 0;
for (j = lin - 1; j < lin + 2; j++)
{
if (mine[i][j] == '1')
{
count++;
}
}
}
return count;
}
//递归展开排雷
void brush_mine(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin)
{
int count = num_mine(mine, row, lin);
if (row > 0 && row <= ROW && lin>0 && lin <= LIN)
{
if (count == 0)
{
board[row][lin] = ' ';
int i = 0;
for (i = row + 1; i > row - 2; i--)
{
int j = 0;
for (j = lin - 1; j < lin + 2; j++)
{
if (board[i][j] == '*')
{
brush_mine(board, mine, row, lin);
}
}
}
}
else
{
board[row][lin] = '0' + count;
}
}
}
//判断是否胜利
int win_mine(char board[ROWS][LINS], int row, int lin)
{
int count = 0;
int i = 0;
for (i = 1; i < row - 1; i++)
{
int j = 0;
for (j = 1; j < lin - 1; j++)
{
if (board[i][j] == '*')
{
count++;
}
}
}
if (count == 10)
{
return 1;
}
else
{
return 0;
}
}
//插旗
int flag(char board[ROWS][LINS],char mine[ROWS][LINS], int row, int lin)
{
int count = 0;
board[row][lin] = "|>";
if (mine[row][lin] == '1')
{
return 1;
}
return 0;
}
test.h
#include"game.h"
//开始菜单
void is_start()
{
printf("**********************\n");
printf("***** 1:开始 *****\n");
printf("***** 0:退出 *****\n");
printf("**********************\n");
}
//游戏实现模块
void game()
{
//两个数组一个给玩家看,一个用来埋雷
char mine[ROWS][LINS];//雷
char board[ROWS][LINS];//玩家
//初始化棋盘
init_board(mine, ROWS, LINS, '0');
init_board(board, ROWS, LINS, '*');
//打印棋盘
print_board(board, ROW, LIN);
//埋雷
bury_mine(mine, ROW, LIN);
//print_board(mine, ROWS, LINS);
//排查坐标是否合法
while (1)
{
int x = 0;
printf("选择排查(1)还是插旗(2):>");
scanf("%d", &x);
if (x == 1)
{
int win = 0;
int i = 0;
int j = 0;
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
//排雷
int n = is_or_no(mine, i, j);
if (n == 1)
{
brush_mine(board, mine, i, j);
print_board(board, ROW, LIN);
win = win_mine(board, ROWS, LINS);
if (win == 1)
{
printf("排雷成功\n");
break;
}
}
else
{
printf("你被炸死了,游戏结束\n");
break;
}
}
else
{
printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}
}
else if (x == 2)
{
int win = 0;
int i = 0;
int j = 0;
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
int n = flag(board, mine, i, j);
if (n == 1)
{
win++;
}
if (win == 10)
{
printf("排雷成功\n");
break;
}
}
else
{
printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}
}
else
{
printf("选择错误,重新选择\n");
}
}
}
void test()
{
int i = 0;
srand((unsigned int)time(NULL));
do
{
is_start();
printf("请选择:");
scanf_s("%d", &i);
if (i == 1)
{
game();
printf("开始游戏\n");
}
else if (i == 0)
{
printf("退出游戏\n");
}
else
{
printf("选择错误,重新选择\n");
}
} while (i);
}
int main()
{
test();
return 0;
}