您现在的位置是:首页 >技术交流 >扫雷—————(c语言)网站首页技术交流
扫雷—————(c语言)
家人们,上次写了三子棋呢,也是直接宕机了,但是生命不止,代码不止。
今天,我们继续更新用二维数组实现一个经典的游戏--扫雷。
目录
构思整体思路
写之前呢,先看看真正的游戏扫雷是什么样的,我们选择基础版
中间那个黄色笑脸,就是重新开始游戏,左上角的10是雷的个数,右边呢是完成扫雷需要的时间(这里我们可以忽略,因为是简易版的扫雷,要什么自行车),同时我们还要观察,这个棋盘是9*9的棋盘,我们任意点击一个位置,他就会出现一大片没有雷的区域,并且在棋盘上还有这个坐标周围八个方格的雷的个数(还没有玩过扫雷的同学,可以去网上找视频看看怎么玩)
这里我们定义game.h(放函数声明)、game.c(放实现游戏的代码)、test.c(测试游戏的代码)
并且,我们要写一个菜单功能的函数,实现游玩
void menu() {
printf("*****************************
");
printf("******** 1 . Play *********
");
printf("******** 0 . Exit *********
");
printf("*****************************
");
}//菜单
int main() {
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("欢迎下次游玩
");
break;
default:
printf("选择错误,请重新选择
");
break;
}
} while (input);
return 0;
}
这里,我们定义game()函数来实现调用游戏代码,接下来,就声明函数了
#pragma once
#pragma warning (disable:4996)
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//雷的个数,可以手动调整难度
#define EASY_MIND 10
//要扫雷的区域,可手动调整大小,不可调整太多,需要配合调整雷的个数使用,否则会将所有雷区展开
#define ROW 9
#define COL 9
//实际的区域
#define ROWS ROW+2
#define COLS COL+2
//菜单
void menu();
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//统计雷的数量
int GetMineCount(char mine[ROWS][COLS], int x, int y);
//递归展开无雷区域
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
1.输出棋盘
思考:
1.要初始化输出的棋盘是怎么样的
2.怎么样初始化棋盘
3.雷怎么埋
话不多说,直接上代码
game.c
初始化棋盘
思考:
1.初始化的棋盘是几乘几的
2.初始化的符合定义为什么
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret) {
int i = 0;
int j = 0;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
board[i][j] = ret;
}
}
}
1.其中,我们定义一个mine数组,里面放需要埋的雷,show数组放棋盘,所以说这两个数组必须是一模一样的,这样子我们就通过棋盘来扫雷了,我们初始化棋盘都为‘*’,雷为‘0’(就是没有雷,为什么是‘0’,后而不是0,后面会解释)。
2.我们到底要初始化几乘几呢,这个问题很好理解,扫雷的核心玩法就是你选的这个坐标的九宫格里面有多少个雷,从而一步步将雷扫光。中间如果出现一个1,那说明周围八个坐标雷的总数就是1个,如果有两个,就会显示2........依此类推,所以说最多的雷个数是8个,当然这个概率可能会非常小,可以忽略。
所以说我们要初始化的棋盘,这个坐标周围一定要有八个坐标,由于我们选择的是9*9的大小,所以说四条边周围一定要有坐标,所以说我们实际上初始化的棋盘必须是11*11的才能保证9*9的棋盘的四个边的格子周围有八个坐标。否则在后面统计雷的数量,就会造成数组越界访问
输出棋盘
思考:
1.输出的棋盘是几乘几的
2.怎么样在棋盘给玩家加上坐标
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
int i = 0;
int j = 0;
printf("--------扫雷游戏-------
");
for (i = 0; i <= col; i++) {
printf("%d ", i);
}//打印下标
printf("
");
for (i = 1; i <= row; i++) {
printf("%d ", i);
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("
");
}
}
1.输出的棋盘当然是9*9的大小啦
2.在棋盘周围加上坐标,让玩家准确选择坐标,所以说在最开头的打印行的时候,加入一个for循环输出行坐标即可,在打印完行的时候,打印列坐标。让我们看看代码效果
这里我们把show传进去,就可以美美打印棋盘跟坐标。
棋盘打印完了接下来就是扫雷了!!!
2.布置雷
1.雷要怎么放
2.要放多少个雷
先来看代码
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col) {
//布置10个雷
//随机生成
int count = EASY_MIND;//雷的数量
while (count) {
int x = rand() % row + 1;//生成随机坐标
int y = rand() % col + 1;//生成随机坐标
if (board[x][y] == '0') {
board[x][y] = '1';
count--;//生成一个雷就减少一次
}
}//循环设置雷
}
1.采用随机数生成坐标,从而在这个坐标放雷,所以说雷的坐标不能超过9*9的棋盘,如果是放在11*11的数组里面,会造成错误(会让游戏无法游玩)。
2.我们把雷赋值为‘1’,所以说当我们扫到这个坐标的时候,我们就可以把他周围八个坐标的雷加起来了。为什么不是数字呢,是因为我们定义棋盘为‘*’(char类型),为了一致,我们也要让雷也是char类型,当然你也可以选择int类型,原理其实都一样。其中,定义雷的数量为count,每生成一个,雷的数量就减少一个,这样子我们就可以把雷放好了,我们看看雷的效果,用打印棋盘的函数来打印mine数组。
DisplayBoard(mine, ROW, COL);
芜湖,睁大眼睛数一数刚好十个。 雷放好了,接下来就是怎么扫了。
排雷
思考
1.扫雷的范围是多少
2.怎么样计算x,y周围八个坐标雷的数量
3.怎么样才能算扫完雷
int GetMineCount(char mine[ROWS][COLS], int x, int y) {
return (mine[x][y + 1] + mine[x][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x + 1][y - 1] +
mine[x - 1][y] + mine[x - 1][y + 1] + mine[x - 1][y - 1] - 8 * '0');
}//返回八个坐标的雷
//因为是字符类型,所以说要转换成整形就需要减去'0'的ASCLL码值,八个就减去8*48(‘0’)
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
//排查坐标
int x = 0;
int y = 0;
while (win < row * col - EASY_MIND) {
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (mine[x][y] == '1') {
printf("恭喜你被炸死了!
");
printf("请重新游玩
");
DisplayBoard(mine, ROW, COL);//把雷打印出来让你死的明白
break;
}
else {
Expand(mine, show, x, y);//递归展开雷
DisplayBoard(show, ROW, COL);
}
}
else {
printf("坐标非法,请重新输入!
");
}
}
if (win == row * col - EASY_MIND)
printf("排雷成功!
");
}
1.跟前面的三子棋一样,应用到这里原理也是一样的,如果踩到雷就死,结束循环,没有踩到雷就继续扫雷。
2.直接返回x,y周围八个坐标相加在减去相应的ASCLL码值即可
3.怎么样判断输赢呢?首先,我们要实现展开一片无雷区域的效果,最简单的方法就是使用递归。
直接上代码
条件
1.该坐标本身就不是雷
2.该坐标周围没有雷:if(count==0),count是x,y周围坐标的雷的数量
3.该坐标没有展开过,就是if(show[x][y]=='*')的时候就可以展开无雷区域
//展开无雷区域的条件
//1.该坐标本身就不是雷
//2.该坐标周围没有雷:if(count==0),count是x,y周围坐标的雷的数量
//3.该坐标没有展开过,就是if(show[x][y]=='*')的时候就可以展开无雷区域
int win = 0;//这里定义全局变量才能实现Expand()函数的功能
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
int count = GetMineCount(mine, x, y);//查看周围是否有雷
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '*') { // 原来是未知才继续展开
win++; // 统计已经展开的点数
if (count == 0) {
show[x][y] = ' '; // 设置为空白字符
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++) {
for (j = y - 1; j <= y + 1; j++) {
if (i >= 1 && i <= ROW && j >= 1 && j <= COL) {
Expand(mine, show, i, j);
}//遍历x,y周围八个坐标
}
}
}
else {
show[x][y] = count + '0';
}
}//在棋盘的未展开区域递归
}//要在棋盘内递归
}//正确版本
换个思路来想,我们把不是雷的区域全部找出来,剩下的就是雷了,所以说我们要赢,就需要一个变量来统计无雷的区域,因此定义一个全局变量win(因为是两个函数需要使用到win),每递归一次,win++一次,当递归碰到雷的时候就停止递归,并且把无雷区域重新赋值为空白符号,返回周围八个坐标雷的个数。当win达到条件的时候就直接退出循环判断输赢。
接下来乱杀电脑!
源码
game.h
#pragma once
#pragma warning (disable:4996)
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//雷的个数,可以手动调整难度
#define EASY_MIND 10
//要扫雷的区域,可手动调整大小,不可调整太多,需要配合调整雷的个数使用,否则会将所有雷区展开
#define ROW 9
#define COL 9
//实际的区域
#define ROWS ROW+2
#define COLS COL+2
//菜单
void menu();
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//统计雷的数量
int GetMineCount(char mine[ROWS][COLS], int x, int y);
//递归展开无雷区域
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
game.c
#include "game.h"
void menu() {
printf("*****************************
");
printf("******** 1 . Play *********
");
printf("******** 0 . Exit *********
");
printf("*****************************
");
}//菜单
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret) {
int i = 0;
int j = 0;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
board[i][j] = ret;
}
}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
int i = 0;
int j = 0;
printf("--------扫雷游戏-------
");
for (i = 0; i <= col; i++) {
printf("%d ", i);
}//打印下标
printf("
");
for (i = 1; i <= row; i++) {
printf("%d ", i);
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("
");
}
}
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col) {
//布置10个雷
//随机生成
int count = EASY_MIND;//雷的数量
while (count) {
int x = rand() % row + 1;//生成随机坐标
int y = rand() % col + 1;//生成随机坐标
if (board[x][y] == '0') {
board[x][y] = '1';
count--;//生成一个雷就减少一次
}
}//循环设置雷
}
//递归展开无雷区
//void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
// int count = GetMineCount(mine, x, y);
// if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
//
// if (count == '0') {
// show[x][y] = ' ';
// int i = 0;
// int j = 0;
// for (i = x - 1; i <= x + 1; i++) {
// for (j = y - 1; j <= y + 1; j++) {
// if (show[x][y] == '*') {
// Expand(mine, show, x, y);
// }
// }
// }
// }
// }
// else {
// show[x][y] = count + '0';
// }
//}//错误版本
//展开无雷区域的条件
//1.该坐标本身就不是雷
//2.该坐标周围没有雷:if(count==0),count是x,y周围坐标的雷的数量
//3.该坐标没有展开过,就是if(show[x][y]=='*')的时候就可以展开无雷区域
int win = 0;//这里定义全局变量才能实现Expand()函数的功能
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
int count = GetMineCount(mine, x, y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '*') { // 原来是未知才继续展开
win++; // 统计已经展开的点数
if (count == 0) {
show[x][y] = ' '; // 设置为空白字符
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++) {
for (j = y - 1; j <= y + 1; j++) {
if (i >= 1 && i <= ROW && j >= 1 && j <= COL) {
Expand(mine, show, i, j);
}//遍历x,y周围八个坐标
}
}
}
else {
show[x][y] = count + '0';
}
}//在棋盘的未展开区域递归
}//要在棋盘内递归
}//正确版本
int GetMineCount(char mine[ROWS][COLS], int x, int y) {
return (mine[x][y + 1] + mine[x][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x + 1][y - 1] +
mine[x - 1][y] + mine[x - 1][y + 1] + mine[x - 1][y - 1] - 8 * '0');
}//返回八个坐标的雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
//排查坐标
int x = 0;
int y = 0;
while (win < row * col - EASY_MIND) {
DisplayBoard(mine, ROW, COL);
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (mine[x][y] == '1') {
printf("恭喜你被炸死了!
");
printf("请重新游玩
");
DisplayBoard(mine, ROW, COL);
break;
}
else {
Expand(mine, show, x, y);
DisplayBoard(show, ROW, COL);
}
}
else {
printf("坐标非法,请重新输入!
");
}
}
if (win == row * col - EASY_MIND)
printf("排雷成功!
");
win =0;//重置
}
test.c
#include "game.h"
void game() {
char mine[ROWS][COLS] = { 0 };//存放布置好的雷
char show[ROWS][COLS] = { 0 };//实现扫雷的数组
//初始化棋盘
//1.mine最开始全是'0'
//2.show最开始全是'*'
//初始棋盘时,要注意要初始化11*11的
//如果只初始化9*9,在遍历八个坐标时会造成越界访问,造成未知的错误
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
/*DisplayBoard(mine, ROW, COL);*///打印的是show数组
DisplayBoard(show, ROW, COL);
//1.布置雷
SetMine(mine, ROW, COL);
/*DisplayBoard(mine, ROW, COL);*/
//2.排查雷
FindMine(mine, show, ROW, COL);
}
int main() {
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("欢迎下次游玩
");
break;
default:
printf("选择错误,请重新选择
");
break;
}
} while (input);
return 0;
}
总结
实现了扫雷的部分基本功能, 但是没有实现雷的标记,还有对难度的选择。对于递归部分还不够深入了解,当然这部分也可以用循环来解决,但是博主尚未学会使用,还需要继续努力。