扫雷是一个经典的游戏,是一个益智类小游戏,在80、90年代曾风靡一时,当然现在也是十分受欢迎;
废话不多说;
让我们来了解一下如何用C语言去实现它吧!!!(❁´◡`❁)
运行环境:VS2019
先实现一个初级版本的:
1、我们得有一个9*9的棋盘;
2、我们在棋盘上布置10颗雷;
3、搜索周围的雷,并且排查雷;
4、判断输赢;
这便是主要的大体思路;接下来我们来依次解决它!!!
首先既然要生成99的棋盘,二维数组没跑了!!!
虽说看起来我们要生成一个99的棋盘,但是实际上我们如果真的生成了99的棋盘真的方便吗?
其实是并不是那么方便;
假设我们生成了一个99的棋盘,那我们每次去判断一个位置周围的雷的情况的时候很复杂,如果我们要排的位置比较靠近中间还好,不用担心数组越界的问题;但是我们要排的位置不靠近中心,相反它处于边缘位置,那我们每次去搜索它周围的雷的时候是不是每次都得先判断数组越没越界,再才进行雷的统计,实属相当之麻烦。那么有没有什么好办法既可以解决数组越界的问题又能方便雷的统计呢?
既然不能真的生成一个99的棋盘,那我们就生成一个伪的,我们生成一个1111的可好?
我们把真正的棋盘套在里面;我们把外面留着,方便后面统计雷的个数;
这样对于边缘角落的雷的统计我们就能轻易的完成,又不用担心越界,多美事一件;
那么好现在棋盘有了,我们得展示给玩家看啊!我们直接将雷盘展示给玩家?当然不是?我们带另外再开一个棋盘,专门给玩家看的;如果我们不在开一个棋盘的话,给玩家看到的肯定不能是雷盘对不对?那我们是不是就得给棋盘上一层”锁“来隐藏地雷对不对?可是如果我们真的像这样操作了的话,我们的雷就会被我们的“锁”给弄没了,雷都没了还扫个屁的雷啊!既然如此,我们应该开辟一个与雷盘一模一样大小的“锁盘”,来与雷盘映射起来,我们的锁盘上可以用来记录“锁”,排查位置周围雷的情况、和做标记;
类似于这样;
那我们就定义两个棋盘出来呗:
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
为了后续的方便我们将行和列都定义成宏,方便后期代码的维护和级别的选择;
既然棋盘都有了,那肯定要初始化嘛;
mine数组(不展示给玩家)没有地雷的时候,全部初始化为空格,有雷时将空格改为# 就行了;
show数组(展示给玩家)再给玩家看第一次的时候应该全部初始化为 * 来为雷盘上锁;后面根据排雷的情况我们依次进行替换;
初始化函数:
void init_board(char a[][COLS], int r, int c, char set)//要初始化成的set初始化的字符
{
int i = 1;
int j = 1;
for (i = 1; i <= r; i++)
{
for (j = 1; j <= c; j++)
{
a[i][j] = set;
}
}
}
既然初始化都完成了:那我们还得打印出来看看:
于是我们在写给打印棋盘的函数:
void display_board(char a[][COLS], int r, int c)
{
int i = 1;
int j = 1;
printf(" ");
for (j = 0; j <= c; j++)//这个时打印列标
{
if(j<10)
printf("%d ",j);
else
{
printf("%c ",'A'+j-10);//超过10,自动用16进制表示;
}
}
putchar('\n');
for (j = 0; j <= c; j++)//打印横向,将棋盘与列标分开;
{
printf("--");
}
putchar('\n');
for (i = 1; i <= r; i++)//至此才开始打印数组元素;
{
if (i < 10)//每次打印之前,先打印一下横标;
printf("%d| ", i);
else
{
printf("%c| ", 'A' + i - 10);
}
for (j = 1; j <= c; j++)//
{
printf("%c ",a[i][j]);
}
printf("\n");
}
}
效果图:
既然展示给玩家看到和存放地雷的棋盘都准备好了,那我们就应该往雷盘里面防雷了不是;
防雷肯定是随机放的,我们也不知道:那么肯定得用到rand()来产生随机数了,于是我们防雷的函数就可以写出:
void set_mine(char mine[][COLS], int r, int c,int count)
{
int x = 0;
int y = 0;
while (count)
{
x = rand() % r + 1;
y = rand() % c + 1;
if (mine[x][y] == ' ')
{
mine[x][y] = '#';
count--;
}
}
}
当然为了后续方便我们选择将地雷的数量也定义成宏的形式:
我们来运行看看是不是真有10颗地雷:
数了数没问题,程序还没有处bug,接着往下写;
既然地雷也布置好了,那我们就应该排查地雷了不是;
我们怎么排查呢?
我们对应的点应该输入合法坐标>
我们输入的坐标应该未被排查过>
如果上述都满足,我们再来判断是否需要标记,如果我们输入Y表示我们需要对该坐标进行标记,输入其它字符默认不需要标记>
如果我们上述都满足,我们也标记过来,而接下来这个位置刚好是地雷,那么游戏直接结束;如果这个位置不是地雷,则这个位置将会被替换成该位置周围地雷存在的数量(不包含该位置),当然如果再此等情况下,该位置的周围地雷的数量为0,则我们可以将此位置周围一片都展示出来,如果该位置的周围位置含满足上述情况,则继续展开,直至不能展开为止(因此像这种情况我们可以用递归来描写);(类似于下图)
由此我们的代码可以这样写:
void find_mine(char show[][COLS],char mine [][COLS], int r, int c)//看看能不能在优化;
{
int x = 0;
int y = 0;
char ch = 0;
while (1)
{
printf("请输入你要排查的位置,并选择是否对该点进行标识(Y\\其它默认不标记)>");//对同一个点多次标记会取消标记;
scanf("%d %d %c", &x, &y,&ch);
if (x >= 1 && x <= r && y >= 1 && y <= c)//坐标合法
{
if (!is_find(show, x, y))//未被排查过
{
if (show[x][y] == 'M' && 'Y' == ch)
{ show[x][y] = '*';
printf("取消标记成功\n");
system("cls");
display_board(show,r,c);
break;
}
if ('Y' == ch)//判断一下是否需要标记;
{
show[x][y] = 'M';
printf("标记成功\n");
system("cls");
display_board(show, r, c);
break;
}
else
{
if (is_mine(mine, x, y))//该位置是地雷
{
printf("你被雷炸死了\n\a\a\a");
show[x][y] = '#';//将炸死的回访展示出来
flag = 0;
system("cls");
display_board(mine, r, c);
break;
}
else//不是雷
{
surprise(mine, show, r, c, x, y);//进一步判断该位置周围雷的数量,若为0,进行进一步展开
system("cls");
display_board(show, r, c);
break;
}
}
}
else
{
printf("坐标已排查过,请重新输入:>\n");
}
}
else
printf("坐标非法,请重新输入\n");
}
}
int get_mine(char show[][COLS], char mine[][COLS], int x, int y)//返回(x,y)位置周围雷的数量
{
int count = 0;
if (mine[x - 1][y - 1] == '#')
count++;
if (mine[x - 1][y] == '#')
count++;
if (mine[x - 1][y + 1] == '#')
count++;
if (mine[x ][y - 1] == '#')
count++;
if (mine[x][y + 1] == '#')
count++;
if (mine[x +1][y - 1] == '#')
count++;
if (mine[x + 1][y] == '#')
count++;
if (mine[x + 1][y + 1] == '#')
count++;
return count;//如果大家还有更好的统计地雷的方法,可以按照自己的方式来,博主的方式比较笨,但是好理解
}
bool is_mine(char mine[][COLS], int x, int y)//判断(x,y)位置是不是地雷
{
if (mine[x][y] == ' ')
return false;
return true;
}
bool is_find(char show[][COLS], int x, int y)//判断(x,y)位置是否被排查过
{
if (show[x][y] == '*'|| show[x][y] == 'M')
return false;
return true;
}
void surprise(char mine[][COLS], char show[][COLS], int r, int c, int x, int y)
{
if (!(x >= 1 && x <= r && y >= 1 && y <= c))//防止越界;
return;
if (is_mine(mine, x, y) ||is_find(show, x, y))//能展开的条件:1、没被排查过;2、排查位置不是雷
return;
setp--;//如果该位置不是雷,则最多步数 -1;
int count = get_mine(show, mine, x, y);//3、排查位置周围没有雷;
show[x][y] = count + '0';//将数字转换为数字字符
if (count == 0)
{
surprise(mine, show, r, c, x - 1, y - 1);//八个角落依次展开
surprise(mine, show, r, c, x - 1, y);
surprise(mine, show, r, c, x - 1, y + 1);
surprise(mine, show, r, c, x, y - 1);
surprise(mine, show, r, c, x, y + 1);
surprise(mine, show, r, c, x + 1, y - 1);
surprise(mine, show, r, c, x + 1, y);
surprise(mine, show, r, c, x + 1, y + 1);
}
}
为了判断输赢我定义了两个全局变量来表示:
因此判断输赢的代码我们可以这样写:
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
init_board(mine,ROW,COL,' ');//初始化
init_board(show, ROW, COL, '*');//初始化
set_mine(mine, ROW, COL, MINE_COUNT);//布置雷
//display_board(mine, ROW, COL);//开透视
display_board(show, ROW, COL);//展示棋盘(给玩家看);
setp = ROW * COL - MINE_COUNT;
flag = 1;//重置一下,开始新的游戏
while (setp&&flag)
{
find_mine(show, mine, ROW, COL);
}
if (!setp)
{
system("cls");
printf("挑战成功\n");
}
else if(!flag)
{
system("cls");
printf("挑战失败\n");
}
display_board(show, ROW, COL);//展示棋盘(给玩家看);
for (int j = 0; j <= COL; j++)
{
printf("--");
}
putchar('\n');
display_board(mine,ROW,COL);
}
至此游戏至此已经全部完成:
我们最后敲个main函数让游戏活起来:
void menu()
{
printf("********** 扫雷小游戏 ********* "); printf(" 游戏介绍\n");
printf("******** 1. play ******** "); printf("1、两次对同一个点标记,可取消标记。\n");
printf("******** 0. exit ******** "); printf("2、最后一次输入位置若是地雷,则该位置将会被标为#以此告诉玩家死亡原因。\n");
printf("********************************* "); printf("3、若在坐标后面输入Y则表示对该点进行标记,其它默认不标记。\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case PLAY:
system("cls");
game(); break;
case EXIT:
system("cls"); printf("退出游戏\n"); break;
default:
system("cls"); printf("输入有误,重新输入:>\n"); break;
}
} while (input);
return 0;
}
头文件和声明:
#pragma once
#include
#include
#include
#include
#include
#define MINE_COUNT 80//地雷数量
#define ROW 9//
#define COL 9//更改这三部变换难度;
#define ROWS ROW+2
#define COLS COL+2
enum CHOOSE
{
EXIT,
PLAY,
};
extern int flag;
extern int setp;
void init_board(char a[][COLS], int r, int c, char set);//初始化棋盘
void display_board(char a[][COLS], int r, int c);//打印棋盘
void set_mine(char mine[][COLS], int r, int c,int count);//布置雷
void find_mine(char show[][COLS], char mine[][COLS], int r, int c);//排查雷;
int get_mine(char show[][COLS], char mine[][COLS], int r, int c);//得到雷的数量
bool is_mine(char mine[][COLS], int x, int y);//判断下x,y是否为雷;
bool is_find(char show[][COLS], int x, int y);//判断是否被排查过
void surprise(char mine[][COLS], char show[][COLS], int r, int c, int x, int y);//展开一片
主要函数的实现:
#define _CRT_SECURE_NO_WARNINGS 0
#include "game.h"
#pragma warning (disable:6031)
void init_board(char a[][COLS], int r, int c, char set)
{
int i = 1;
int j = 1;
for (i = 1; i <= r; i++)
{
for (j = 1; j <= c; j++)
{
a[i][j] = set;
}
}
}
void display_board(char a[][COLS], int r, int c)
{
int i = 1;
int j = 1;
printf(" ");
for (j = 0; j <= c; j++)
{
if(j<10)
printf("%d ",j);
else
{
printf("%c ",'A'+j-10);
}
}
putchar('\n');
for (j = 0; j <= c; j++)
{
printf("--");
}
putchar('\n');
for (i = 1; i <= r; i++)
{
if (i < 10)
printf("%d| ", i);
else
{
printf("%c| ", 'A' + i - 10);
}
for (j = 1; j <= c; j++)
{
printf("%c ",a[i][j]);
}
printf("\n");
}
}
void set_mine(char mine[][COLS], int r, int c,int count)
{
int x = 0;
int y = 0;
while (count)
{
x = rand() % r + 1;
y = rand() % c + 1;
if (mine[x][y] == ' ')
{
mine[x][y] = '#';
count--;
}
}
}
void find_mine(char show[][COLS],char mine [][COLS], int r, int c)//看看能不能在优化;
{
int x = 0;
int y = 0;
char ch = 0;
while (1)
{
printf("请输入你要排查的位置,并选择是否对该点进行标识(Y\\其它默认不标记)>");//对同一个点多次标记会取消标记;
scanf("%d %d %c", &x, &y,&ch);
if (x >= 1 && x <= r && y >= 1 && y <= c)//坐标合法
{
if (!is_find(show, x, y))//未被排查过
{
if (show[x][y] == 'M' && 'Y' == ch)
{ show[x][y] = '*';
printf("取消标记成功\n");
system("cls");
display_board(show,r,c);
break;
}
if ('Y' == ch)//判断一下是否需要标记;
{
show[x][y] = 'M';
printf("标记成功\n");
system("cls");
display_board(show, r, c);
break;
}
else
{
if (is_mine(mine, x, y))//该位置是地雷
{
printf("你被雷炸死了\n\a\a\a");
show[x][y] = '#';//将炸死的回访展示出来
flag = 0;
system("cls");
display_board(mine, r, c);
break;
}
else//不是雷
{
surprise(mine, show, r, c, x, y);//进一步判断该位置周围雷的数量,若为0,进行进一步展开
system("cls");
display_board(show, r, c);
break;
}
}
}
else
{
printf("坐标已排查过,请重新输入:>\n");
}
}
else
printf("坐标非法,请重新输入\n");
}
}
int get_mine(char show[][COLS], char mine[][COLS], int x, int y)
{
int count = 0;
if (mine[x - 1][y - 1] == '#')
count++;
if (mine[x - 1][y] == '#')
count++;
if (mine[x - 1][y + 1] == '#')
count++;
if (mine[x ][y - 1] == '#')
count++;
if (mine[x][y + 1] == '#')
count++;
if (mine[x +1][y - 1] == '#')
count++;
if (mine[x + 1][y] == '#')
count++;
if (mine[x + 1][y + 1] == '#')
count++;
return count;
}
bool is_mine(char mine[][COLS], int x, int y)
{
if (mine[x][y] == ' ')
return false;
return true;
}
bool is_find(char show[][COLS], int x, int y)
{
if (show[x][y] == '*'|| show[x][y] == 'M')
return false;
return true;
}
void surprise(char mine[][COLS], char show[][COLS], int r, int c, int x, int y)
{
if (!(x >= 1 && x <= r && y >= 1 && y <= c))//防止越界;
return;
if (is_mine(mine, x, y) ||is_find(show, x, y))//能展开的条件:1、没被排查过;2、排查位置不是雷
return;
setp--;
int count = get_mine(show, mine, x, y);//3、排查位置周围没有雷;
show[x][y] = count + '0';
if (count == 0)
{
surprise(mine, show, r, c, x - 1, y - 1);
surprise(mine, show, r, c, x - 1, y);
surprise(mine, show, r, c, x - 1, y + 1);
surprise(mine, show, r, c, x, y - 1);
surprise(mine, show, r, c, x, y + 1);
surprise(mine, show, r, c, x + 1, y - 1);
surprise(mine, show, r, c, x + 1, y);
surprise(mine, show, r, c, x + 1, y + 1);
}
}
main函数:
#define _CRT_SECURE_NO_WARNINGS 0
#include"game.h"
#pragma warning (disable:6031)
//1、雷盘,展示盘
//2、初始化雷盘和展示盘//空格表示没雷#表示雷;*初始化展示盘
//3、打印一下棋盘;
//4、布置雷;
//5、排查雷;(注意触发惊喜)
int setp = ROW * COL - MINE_COUNT;//记录排雷最多步数;当setp等于0是表示安全位置已经排完,挑战成功
int flag = 1;//表示未被地雷炸死而结束游戏,地雷炸死而结束游戏的flag=0,挑战失败;
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
init_board(mine,ROW,COL,' ');//初始化
init_board(show, ROW, COL, '*');//初始化
set_mine(mine, ROW, COL, MINE_COUNT);//布置雷
//display_board(mine, ROW, COL);//开透视
display_board(show, ROW, COL);//展示棋盘(给玩家看);
setp = ROW * COL - MINE_COUNT;
flag = 1;//重置一下,开始新的游戏
while (setp&&flag)
{
find_mine(show, mine, ROW, COL);
}
if (!setp )
{
system("cls");
printf("挑战成功\a\a\a\a\n");
}
else if(!flag)
{
system("cls");
printf("挑战失败\a\a\a\n");
}
display_board(show, ROW, COL);//展示棋盘(给玩家看);
for (int j = 0; j <= COL; j++)
{
printf("--");
}
putchar('\n');
display_board(mine,ROW,COL);
}
void menu()
{
printf("********** 扫雷小游戏 ********* "); printf(" 游戏介绍\n");
printf("******** 1. play ******** "); printf("1、两次对同一个点标记,可取消标记。\n");
printf("******** 0. exit ******** "); printf("2、最后一次输入位置若是地雷,则该位置将会被标为#以此告诉玩家死亡原因。\n");
printf("********************************* "); printf("3、若在坐标后面输入Y则表示对该点进行标记,其它默认不标记。\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case PLAY:
system("cls");
game(); break;
case EXIT:
system("cls"); printf("退出游戏\n"); break;
default:
system("cls"); printf("输入有误,重新输入:>\n"); break;
}
} while (input);
return 0;
}