c语言写扫雷是一个很不错的练习方式,接下来由我为大家介绍一下关于扫雷的代码
扫雷这款游戏的代码逻辑包括创建棋盘,打印棋盘,埋雷,找雷。
游戏规则:
在一个棋盘内排查地雷,但是地雷不会让玩家看到,并且每个地雷的位置都不重复,如果玩家踩到了地雷,则游戏结束并让玩家选择是否重新开始,如果玩家没有踩到雷并且该坐标周围也没有雷,则展开一片区域,并在这片区域的边缘标记上周围雷的数量,但是玩家不能走已走过的区域,玩家也不能输入在棋盘之外的坐标。最后还要判断游戏的输赢,如果除了有雷的位置其他全部都排查完毕,那么游戏获胜
注意!
注意!
注意!
多看注释!
如果你们对于下面的内容有什么不懂的地方可以提问,我尽量为你们解答
main函数内第一行先不要管,一会儿会讲,要使用一个do...while循环开始游戏,这样做的好处谁用谁知道,你还需要定义一个开始菜单,这个菜单的样式你们可以自己决定,如果你觉得玩完一把不过瘾想要再玩一把就一定要把它放在循环里,循环的判断条件要使用你自己定义的变量,我这里是input,用它接收你输入的值 ,再使用switch语句,输入1就进入game函数,输入0就退出,输入其他数字就接着循环。
#include"mine.h"
void meau()
{
printf("*******************\n");
printf("****** 1.play *****\n");
printf("****** 0.exit *****\n");
printf("*******************\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
meau();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
game函数这里是写出游戏游戏逻辑的地方,以下是游戏的所有逻辑
void game()
{
char mine[ROWS][COLS] = { 0 };
char check[ROWS][COLS] = { 0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');//'0'和'*'表示mine和check棋盘分别被初始化为'0'和'*'
InitBoard(check, ROWS, COLS, '*');
//埋雷
Burying_a_mine(mine,ROW,COL);
//打印棋盘
DisplayBoard(check, ROW, COL);
DisplayBoard(mine, ROW, COL);//只打印中间的9*9的棋盘
//排雷
FineMine(mine, check, ROW, COL);
}
我把game里的函数的实现也都放在这里了,后面有关于这些函数的讲解
#include"mine.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//set接收'0'和'*'
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)//注意一下这两个for如果i<=rows就会导致栈损坏,也就是数组访问越界
{
for (j = 0; j < cols; j++)//同上
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0, j = 0;
for (j = 0; j <= col; j++)
{
printf("%d ",j);
}
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 Burying_a_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_MINE;//要布置的雷的总个数
while (count)
{
int x = rand() % row + 1;//+1的原因是rand()%row生成的余数为0—8,加上个1,就变成1—9了
int y = rand() % row + 1;
if (mine[x][y]=='0')//防止雷的位置放置重复
{
mine[x][y] = '1';//用字符1表示雷
count--;//每放置一个雷就减1
}
}
}
int Number_of_mines(char mine[ROWS][COLS],char check[ROWS][COLS], int x, int y)
{
int i = 0, j = 0;
int count = 0;//记录周围雷的数量
for (i = -1; i <= 1; i++)//遍历输入坐标的周围8个坐标
{
for (j = -1; j <= 1; j++)
{
if (mine[x - i][y - j] == '1')
count++;
}
}
return count;
}
void Spread_out_a_piece(char mine[ROWS][COLS], char check[ROWS][COLS], int x, int y)
{
int ret = Number_of_mines(mine, check, x, y);
if (ret == 0)//如果该坐标周围没有雷
{
int i = 0, j = 0;
for (i = -1; i <= 1; i++)//遍历该坐标周围的8个坐标
{
for (j = -1; j <= 1; j++)
{
if ((x + i) > 0 && (x + i) < ROWS && (y + j) > 0 && (y + j) < COLS && check[x + i][y + j] == '*')//划定一个边界,并判断该坐标是否被排查过
{
check[x][y] = ' ';
Spread_out_a_piece(mine, check, x + i, y + j);//创造一个新的起点
}
}
}
}
else//如果有雷
check[x][y] = ret + '0';
}
void FineMine(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 0;
int num = EASY_MINE;
while (win < row * col - num)//判断输赢
{
win = 0;//防止win被重复计算
for (int i = 1; i <=ROW; i++)//判断输赢,当所有的非雷格子全部被排查完
{
for (int j = 1; j <= COL; j++)
{
if (check[i][j] != '*')
{
win++;
if (win == row * col - EASY_MINE)
goto The_end;
}
}
}
sb:
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断输入坐标是否在棋盘范围内
{
if (mine[x][y] == '1')//判断该坐标是否为雷
{
printf("恭喜你,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
if (check[x][y] != '*')
{
printf("输入重复,请重新输入\n");
goto sb;
}
int count = Number_of_mines(mine,check, x, y);//判断改坐标周围雷的数量
//显示出该坐标周围地雷的数量,并转换为字符
check[x][y] = count + '0';
Spread_out_a_piece(mine, check, x, y);//每次输入坐标后如果周围没有地雷,就展开一片区域
//注意!!!上面的两个表达式
DisplayBoard(check, ROW, COL);//每次输入坐标后就更新一次棋盘
win++;
}
else
printf("坐标不合法,请重新输入\n");
}
if (win == row * col - EASY_MINE)//设置这个条件就是单纯的不让别人进来,例如说踩雷的那个,跳出循环后不让它进入
{
The_end:
printf("很遗憾,你排雷成功了\n");
DisplayBoard(mine, ROW, COL);//获胜之后给玩家看一下雷的情况
}
}
这里是所有的头文件
#define _CRT_SECURE_NO_WARNINGS
#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
#define EASY_MINE 10
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void Burying_a_mine(char mine[ROWS][COLS], int row, int col);
void FineMine(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col);
接下来我会把他们分开一一去讲,不要忘了引用你的头文件呦
数组定义
这一步也可以叫做创建棋盘,在创建棋盘的时候,你要确定棋盘的大小,棋盘的样式,和棋盘要存储的东西。同时你还要保证你排雷时不会越界
char mine[ROWS][COLS] = { 0 };
char check[ROWS][COLS] = { 0 };
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
mine这个数组是存放地雷的数组,而check这个数组则是展示给玩家看的,ROW和COL是数组展示出来的大小,这里使用了宏定义,方便以后修改,而把他们定义成11*11的则是因为排雷的时候要防止越界,因为排雷的时候需要遍历你输入坐标周围的8个格子,如果你输入(1 ,1)那么该坐标左边的和上边的三个格子就会越界
初始化棋盘
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');//'0'和'*'表示mine和check棋盘分别被初始化为'0'和'*'
InitBoard(check, ROWS, COLS, '*');
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//set接收'0'和'*'
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)//注意一下这两个for如果i<=rows就会导致栈损坏,也就是数组访问越界
{
for (j = 0; j < cols; j++)//同上
{
board[i][j] = set;
}
}
}
我想要把mine数组初始化为'0'所以把字符0传递过去,而check数组我想要把它初始化为'*',所以要把'*'传递过去。而InitBoard函数实现这里就需要创建一个char类型的变量set用于接收'0'和'*'这两个字符,这样可以使用一个函数初始化两个数组。下面使用两个for循环把这两个数组全部初始化。后面有一个加上打印棋盘后的整体效果图
打印棋盘
打印棋盘时我们只打印中间的9*9,外面那一圈是为了防止越界而准备的
DisplayBoard(check, ROW, COL);
DisplayBoard(mine, ROW, COL);//只打印中间的9*9的棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0, j = 0;
for (j = 0; j <= col; j++)//打印横坐标
//j如果不从0开始,那么棋盘左上角就会空出一格,很不好看
{
printf("%d ",j);
}
printf("\n");//打印完横坐标就换行
for (i = 1; i <= row; i++)
{
printf("%d ",i);//打印纵坐标
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
//因为初始化时,是初始化成了字符,所以这里要用%c来打印,为了美观一些%c后面最好加一个空格
}
printf("\n");//每打印完一行就换行一次
}
printf("-------扫 雷-------\n");//为了美观而设计的
}
这里传输参数的时候,我们传递了一个11*11的数组,但是后面的参数却只传输了9*9的数据,因为我们只操作中间那9*9的格子,而不是操作那11*11的,11*11它只是防止我们越界的。还是需要一个嵌套的for循环来打印我们的棋盘。
效果图:
布置地雷
布置地雷也只布置在中间的那9*9里,并且你还要防止地雷布置重复
//埋雷
Burying_a_mine(mine,ROW,COL);
void Burying_a_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_MINE;//要布置的雷的总个数
while (count)
{
int x = rand() % row + 1;//+1的原因是rand()%row生成的余数为0—8,加上个1,就变成1—9了
int y = rand() % row + 1;//说一下srand放的地方和原因
if (mine[x][y]=='0')//防止雷的位置放置重复
{
mine[x][y] = '1';//用字符1表示雷
count--;//每放置一个雷就减1
}
}
}
#define EASY_MINE 10
EASY_MINE是需要布置的雷的总数量,在头文件里定义即可,rand%row+1就规范了设置雷的范围,把它放在循环里是因为每次循环都要随机布置一次雷
还记得我在开头说的那句话吗,这里的rand()对应的正是main函数里的srand(),因为如果想要使用rand就必须先使用srand,然后在srand里使用时间戳设置随机数,time(time里面是空指针)是设置随机数生成的函数,想要使用rand,srand和time就必须先引用他们的头文件,就是下面那俩,因为这个main函数一局游戏只会使用一次所以把srand设置在那里
srand((unsigned int)time(NULL));
#include<time.h>
#include<stdlib.h>
排雷
排雷的这个就比较复杂了,但也没有这么难,你要把你输入的坐标划定在棋盘之内和如果不在范围内就怎样,还要防止坐标输入重复,以及如果踩雷就退出游戏
//排雷
FineMine(mine, check, ROW, COL);
void FineMine(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 0;
while (1)
{
win = 0;//防止win被重复计算
for (int i = 1; i <=ROW; i++)//判断输赢,当所有的非雷格子全部被排查完
{
for (int j = 1; j <= COL; j++)
{
if (check[i][j] != '*')
{
win++;
if (win == row * col - EASY_MINE)
goto The_end;
}
}
}
sb:
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断输入坐标是否在棋盘范围内
{
if (mine[x][y] == '1')//判断该坐标是否为雷
{
printf("恭喜你,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
if (check[x][y] != '*')
{
printf("输入重复,请重新输入\n");
goto sb;//输入重复就会回去重新输入
}
int count = Number_of_mines(mine,check, x, y);//判断改坐标周围雷的数量
//显示出该坐标周围地雷的数量,并转换为字符
check[x][y] = count + '0';
Spread_out_a_piece(mine, check, x, y);//每次输入坐标后如果周围没有地雷,就展开一片区域
//注意!!!上面的两个表达式
DisplayBoard(check, ROW, COL);//每次输入坐标后就更新一次棋盘
win++;
}
else
printf("坐标不合法,请重新输入\n");
}
if (win == row * col - num)
{
printf("很遗憾,你排雷成功了\n");
DisplayBoard(mine, ROW, COL);//获胜之后给玩家看一下雷的情况
}
}
先介绍最初始的这个,稍后介绍里面的两个函数。 Number_of_mines(mine,check, x, y)函数的作用是判断改坐标周围雷的数量,Spread_out_a_piece(mine, check, x, y)函数的作用是展开
这里传递参数要把两个数组都传递过来,因为我们要在mine数组里找雷,在check数组里展示
先定义两个变量,这是你的坐标,之后既然是排雷,而你又不可能一下子就排完吧,所以要用一个循环,先用一个if语句划定你输入坐标的范围,把它划定再你的棋盘之内,再判断输入坐标是否为雷,是雷就重新来过,不是就接着执行下一步,也要判断是否输入重复。在Number_of_mines函数下面的那个表达式是,接收count的返回值并把数字转换为数字字
排查坐标周围是否有雷
然后就是Number_of_mines这个函数
int Number_of_mines(char mine[ROWS][COLS],char check[ROWS][COLS], int x, int y)
{
int i = 0, j = 0;
int count = 0;//记录周围雷的数量
for (i = -1; i <= 1; i++)//遍历输入坐标的周围8个坐标
{
for (j = -1; j <= 1; j++)
{
if (mine[x - i][y - j] == '1')
count++;
}
}
return count;//注意这里返回的是数字,前面打印用的是%c,所以还要转换为字符
}
也是一个嵌套的for循环使用if判断周围8个坐标是否有雷,如果有就count+1最后再返回count的值。
展开
再接着就是Spread_out_a_piece这个展开函数,这里是我认为在整个排雷过程中最重要的内容
void Spread_out_a_piece(char mine[ROWS][COLS], char check[ROWS][COLS], int x, int y)
{
int ret = Number_of_mines(mine, check, x, y);
if (ret == 0)//如果该坐标周围没有雷
{
int i = 0, j = 0;
for (i = -1; i <= 1; i++)//以该坐标为起点重新判断周围是否有雷
{
for (j = -1; j <= 1; j++)
{
if ((x + i) > 0 && (x + i) < ROWS && (y + j) > 0 && (y + j) < COLS && check[x + i][y + j] == '*')//划定一个边界(1——10),并判断该坐标是否被排查过
{
check[x][y] = ' ';//作用是标记该坐标已被展开过
Spread_out_a_piece(mine, check, x + i, y + j);//创造一个新的起点
}
}
}
}
else//如果有雷
check[x][y] = ret + '0';//标出周围雷的数量
}
先创建一个变量ret用于接收Number_of_mines函数返回的值,再使用if语句判断ret是否等于0(等于0就证明该坐标周围没有雷),如果等于0就进入。再创建两个变量用于遍历该坐标周围的八个坐标是否为'*',如果为'*'就说明未被排查过,然就就是让check数组的该坐标变为空格,读者~你也不想让你的栈溢出吧~
再者就是使用递归了一定要把Spread_out_a_piece函数里的x,y变为x+i,y+j一定!一定!一定!我就搁这吃过亏,我甚至把x+i,y+j写成过x+1,y+1我检查了好久才发现,他们长得实在是太像了,最后就是else把ret不等于0时的情况写好
check[x][y] = count + '0';
Spread_out_a_piece(mine, check, x, y);//每次输入坐标后如果周围没有地雷,就展开一片区域
//注意!!!上面的两个表达式
这个把数字转换为数字字符的表达式与Spread_out_a_piece函数的先后顺续也很有讲究,如果你把Spread_out_a_piece放前面,把check[x][y] = count + '0';后面的话,我想你也不想看到下面对的
情况发生吧~桀桀桀~
判断输赢
因为棋盘是9*9的,如果想要赢就需要把9*9-10,也就是71个无雷的格子全部都排查完,使用嵌套的for循环排查所有的格子,就像前面的打印一样,这里你打印了多少个格子你就要排查多少个格子
win = 0;//防止win被重复计算
for (int i = 1; i <=ROW; i++)//判断输赢,当所有的非雷格子全部被排查完
{
for (int j = 1; j <= COL; j++)
{
if (check[i][j] != '*')
{
win++;
if (win == row * col - EASY_MINE)
goto The_end;
}
}
}
if (win == row * col - EASY_MINE)//设置这个条件就是单纯的不让别人进来,例如说踩雷的那个,跳出循环后不让它进入
{
The_end:
printf("很遗憾,你排雷成功了\n");
DisplayBoard(mine, ROW, COL);//获胜之后给玩家看一下雷的情况
}
对于以上的内容如果有什么不懂的地方可以提问,我尽量为你们解答
以上就是我为大家带来的关于用c语言写扫雷的全部内容了,感谢大家收听...咳...不,观看