一、扫雷游戏介绍
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
本文将从思路构建,逻辑分析,代码实现逐步介绍如何制作扫雷游戏。
本程序采用多文件 方式来实现程序
main.c-------------程序的主程序,游戏主界面
game.c------------游戏的过程实现程序
game.h------------游戏过程函数的头文件
二、游戏过程设计
思考一下,扫雷游戏需要哪些组成部分,需要一个面板,还需要设置雷的位置,还需要排查雷,还要提示周围雷的个数等等。
归根结底我们需要:初始化雷区,展示雷区,设置雷的位置,排查雷,数雷的个数,展开无雷区域
明确目标后,我们可以开始尝试写代码了。
2.1主程序框架
主程序main.c的框架大致展现了整个游戏的逻辑,我们先写这部分
我们可以把所需要的函数现在主程序中展现出来,之后再定义声明。
#include"game.h"
void menu()
{
printf("*********************************\n");
printf("*********************************\n");
printf("*******1.PLAY 0.EXIT***********\n");
printf("*********************************\n");
printf("*********************************\n");
}
void game()
{
char mine[ROWS][COLS] = {0};//存放布置好类
char show[ROWS][COLS]= {0};存放排查出雷的信息
//mine数组 未布置雷为0
Initboard(mine,ROWS,COLS,'0');
//show数组 没排查为*
Initboard(show,ROWS,COLS,'*');
//设置雷
Setmine(mine,ROW,COL);
Displayboard(show,ROW,COL);
//排查雷
Findmine(mine,show,ROW,COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input =0;
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
switch(input)
{
case 1:
printf("Game start\n");
game();
break;
case 0:
printf("GOODBYE!!!!!\n");
break;
default:
printf("Wrong choice!!!\n");
break;
}
} while (input);
return 0;
}
2.2初始化雷区
我们不难发现,涉及矩阵的问题经常会想到二维数组,而在本游戏中,既要设置雷,又要翻开面板看是否有雷,显然一个二位数组不容易操作,那么我们不妨用两个二维数组吧!
首先我们创建两个二位数组,分别为作为雷区和玩家操作面板
雷区二位数组mine,用来设置雷的位置我们用‘1’和‘0’来表示有雷和无雷
玩家二维数组show,我们用‘*’来掩盖雷区,当翻开该格子将显示雷或者翻开周围无雷区域并显示周围雷的个数。
说到这里我们可以先创建两个二维数组
char mine[ROWS][COLS] = {0};//存放布置好类
char show[ROWS][COLS]= {0};存放排查出雷的信息
但是,我们该设置多大的矩阵呢,假设我们做的是9X9的矩阵,或许我们的ROWS和COLS就可以设置为9。但是我们再想想,我们排查雷是显示的数字是该格子周围八个格子所有的雷数,如果我们点按的是矩阵边界的格子,我们在操作排查雷的过程是不就会有数组越界的情况吗??
图例1
假如我们设置9X9那在排查雷的时候包越界的。
图例2
如果矩阵边长比原先多2,那这样排查就不会越界了
所以我们在头文件中这样定义这几个常量
#define ROW 9
#define COL 9
#define ROWS ROW+2///设置11行列的原因时因为,在排查是否有雷的情况时便于查找
#define COLS COL+2///
然后开始写雷区的代码吧
2.1.1代码实现
在这里我们需要Initboard()和Displayboard()函数
Initboard()
void Initboard(char board[ROWS][COLS],int rows,int cols,char symbol)//版面初始化
{
int i;
for(i=0;i<rows;i++)
{
int j=0;
for(j=0;j<cols;j++)
{
board[i][j] = symbol;
}
}
}
这里的原理是遍历整个二维数组,参数是mine或show数组,ROWS和COLS,以及两个二位数组对应的标识符,mine是‘0’,show是‘*’
char mine[ROWS][COLS] = {0};//存放布置好类
char show[ROWS][COLS]= {0};存放排查出雷的信息
//mine数组 未布置雷为0
Initboard(mine,ROWS,COLS,'0');
//show数组 没排查为*
Initboard(show,ROWS,COLS,'*');
Displayboard()
void Displayboard(char board[ROWS][COLS], int rows, int cols) //展示版面
{
int i, j;
printf("----------------GAME--------------------\n");
// 打印列标题,如果不需要可以注释掉
for (j = 0; j <= cols; j++) {
printf("%d ", j);
}
printf("\n");
// 打印棋盘内容
for (i = 1; i <=rows; i++) {
printf("%d ", i ); // 打印行标题,加1是因为棋盘通常从1开始计数
for (j = 1; j <=cols; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("----------------GAME--------------------\n");
}
为了是程序出来更加美观也易于输入坐标,我们还需要打印行列标题和上下分割线。
写完Initboard()和Displayboard()函数, 我们在game.h声明一下
void Initboard(char board[ROWS][COLS],int rows,int cols,char symbol);
void Displayboard(char board[ROWS][COLS],int row,int col);
2.3设置雷的位置
void Setmine(char board[ROWS][COLS], int rows, int cols)//设置雷的位置
{
int count = BOOMNUM;
while(count!=0)
{
int x =rand()%rows+1;
int y =rand()%cols+1;
if(board[x][y]=='0')
{
board[x][y] = '1';
count--;
}
}
}
我们这里传入参数,主要在mine数组上操作,传入mine,ROW,COL
主要思路就是,先设置雷的数量count,再生成随机坐标x,y如果该坐标位置为‘0‘即无雷,那就放入一个雷,然后count--直到count为0即全部放完。
为了增强游戏的可自定义度,我们在头文件里设置一个常量BOOMNUM
#define BOOMNUM 10//雷的个数
注意:设置随机数在博客:【c语言学习】如何实现三子棋+多子棋升级-CSDN博客有提到过
2.4排查雷
void Findmine(char mine[ROWS][COLS],char show[ROWS][COLS],int rows,int cols)//查雷
{
int x=0;
int y=0;
int win =0;//找到费雷个数
while(win<rows*cols-BOOMNUM)
{
printf("输入坐标:>");
scanf("%d %d",&x,&y);
if(x>=1&&x<=rows &&y>=1&&y<=cols)//坐标不越界
{
if(show[x][y]!='*')//排查重复输入的情况
{
printf("已被排查请重新输入\n");
}
else
{
if(mine[x][y]=='1')//该坐标有雷
{
printf("哈哈哈你被炸死了!!!!!\n");
Displayboard(mine,ROW,COL);
break;
}
else该坐标无雷
{
Unfold(mine,show,x,y,&win);//展开无雷区域
Displayboard(show,ROW,COL);//展示面板
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if(win==(rows*cols-(BOOMNUM)))
{
printf("恭喜!!!!游戏胜利\n");
Displayboard(mine,ROW,COL);
}
}
这里的大致思路为:我们设置一个win变量来存放找到雷的个数。这里面rows*cols-BOOMNUM
为非雷区域的个数,当win小于非雷区域个数时开展排雷,当win等于非雷区域时即全部排查完毕游戏胜利。我们先判断坐标是否越界,如果越界就重新输入;如果不越界,那就判断该位置是否被排查过,如果被排查过那就重新输入;否则判断该坐标是否为雷,如果为雷游戏结束,否则展开无雷区域,展示面板;之后进入下一轮排雷。
2.4.1展示无雷区域/展示雷的个数
展示无雷区域需要一个Unfold函数,展示雷的个数需要一个get_count_mine函数.
get_count_mine()
int get_mine_count(char board[ROWS][COLS],int x,int y)//数雷的个数
{
return (board[x-1][y-1]+board[x-1][y]+
board[x-1][y+1]+board[x][y+1]
+board[x-1][y+1]+board[x+1][y]
+board[x+1][y-1]+board[x][y-1]-8*'0');
}
思路:
这里需要传入mine数组在。雷区二维数组中判断。
文章开头有提到,显示雷的个数的原理就是在以该坐标位置为中心的九宫格中,依次遍历除中心外的八个坐标。
既然是要直到有多少个雷,那函数的返回值就应该是int整型,而我们数组里存放的是字符类型,那该如何转换呢?
熟悉ASCII码表的朋友应该知道,十进制整型数字与其对应的字符的ASCII码之前差了48.而48正好就是’0‘的ASCII码,所以在该函数中返回值的最后要减去8*’0‘才能得到整型的返回值。
Unfold()
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win)//无雷展开
{
if (x<1||x>ROW||y<1||y>COL) //判断坐标是否越界
{
return;
}
if (show[x][y]!= '*') //判断是否被排查过
{
return;
}
int count = get_mine_count(mine, x, y); //获取该坐标位四周雷的个数
if (count > 0) //如果周围雷大于零
{
(*win)++; //该区域为非雷,非雷区域数加一
show[x][y] = count + '0'; //该区域填入雷的个数
return;
}
else if (count == 0) //如果无雷
{
(*win)++; //该区域为非雷,非雷区域数加一
show[x][y] = ' '; //该区域填入空格展现,非雷区展开的效果
Unfold(mine, show, x - 1, y, win);//然后对周围八个区域递归
Unfold(mine, show, x - 1, y + 1, win);
Unfold(mine, show, x, y + 1, win);
Unfold(mine, show, x + 1, y + 1, win);
Unfold(mine, show, x + 1, y, win);
Unfold(mine, show, x + 1, y - 1, win);
Unfold(mine, show, x, y - 1, win);
Unfold(mine, show, x - 1, y - 1, win);
}
}
展开一片雷区是一个重复的过程,这篇区域没有雷那就展开,有雷就不展开。所以我们需要判断是否有雷,如果有雷就不执行展开程序,返回到上一层的函数。
每次展开都要判断是否遇到地雷,每次都会执行类似的判断,所以这一功能可以用函数递归实现。
**有人会问为什么需要判断坐标是否越界,在findmine函数里不是已经判断过了吗?
这里判断越界并不只是输入的坐标而是判断该区域周围的坐标是否越界。
**为什么要判断该坐标是否被排查过?
试想一下,红色与蓝色两个区域的交集如果重复判断的话,就会形成死递归。
三、完整代码
game.c
#include"game.h"
void Initboard(char board[ROWS][COLS],int rows,int cols,char symbol)//版面初始化
{
int i;
for(i=0;i<rows;i++)
{
int j=0;
for(j=0;j<cols;j++)
{
board[i][j] = symbol;
}
}
}
void Displayboard(char board[ROWS][COLS], int rows, int cols) //展示版面
{
int i, j;
printf("----------------GAME--------------------\n");
// 打印列标题,如果不需要可以注释掉
for (j = 0; j <= cols; j++) {
printf("%d ", j);
}
printf("\n");
// 打印棋盘内容
for (i = 1; i <=rows; i++) {
printf("%d ", i ); // 打印行标题,加1是因为棋盘通常从1开始计数
for (j = 1; j <=cols; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("----------------GAME--------------------\n");
}
void Setmine(char board[ROWS][COLS], int rows, int cols)//设置雷的位置
{
int count = BOOMNUM;
while(count!=0)
{
int x =rand()%rows+1;
int y =rand()%cols+1;
if(board[x][y]=='0')
{
board[x][y] = '1';
count--;
}
}
}
void Findmine(char mine[ROWS][COLS],char show[ROWS][COLS],int rows,int cols)//查雷
{
int x=0;
int y=0;
int win =0;//找到费雷个数
while(win<rows*cols-BOOMNUM)
{
printf("输入坐标:>");
scanf("%d %d",&x,&y);
if(x>=1&&x<=rows &&y>=1&&y<=cols)
{
if(show[x][y]!='*')//排查重复输入的情况
{
printf("已被排查请重新输入\n");
}
else
{
if(mine[x][y]=='1')
{
printf("哈哈哈你被炸死了!!!!!\n");
Displayboard(mine,ROW,COL);
break;
}
else
{
Unfold(mine,show,x,y,&win);
Displayboard(show,ROW,COL);
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if(win==(rows*cols-(BOOMNUM)))
{
printf("恭喜!!!!游戏胜利\n");
Displayboard(mine,ROW,COL);
}
}
int get_mine_count(char board[ROWS][COLS],int x,int y)//数雷的个数
{
return (board[x-1][y-1]+board[x-1][y]+
board[x-1][y+1]+board[x][y+1]
+board[x-1][y+1]+board[x+1][y]
+board[x+1][y-1]+board[x][y-1]-8*'0');
}
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win)//无雷展开
{
if (x<1||x>ROW||y<1||y>COL) //判断坐标是否越界
{
return;
}
if (show[x][y]!= '*') //判断是否被排查过
{
return;
}
int count = get_mine_count(mine, x, y); //获取该坐标位四周雷的个数
if (count > 0) //如果周围雷大于零
{
(*win)++; //该区域为非雷,非雷区域数加一
show[x][y] = count + '0'; //该区域填入雷的个数
return;
}
else if (count == 0) //如果无雷
{
(*win)++; //该区域为非雷,非雷区域数加一
show[x][y] = ' '; //该区域填入空格展现,非雷区展开的效果
Unfold(mine, show, x - 1, y, win);//然后对周围八个区域递归
Unfold(mine, show, x - 1, y + 1, win);
Unfold(mine, show, x, y + 1, win);
Unfold(mine, show, x + 1, y + 1, win);
Unfold(mine, show, x + 1, y, win);
Unfold(mine, show, x + 1, y - 1, win);
Unfold(mine, show, x, y - 1, win);
Unfold(mine, show, x - 1, y - 1, win);
}
}
game.h
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2///设置11行列的原因时因为,在排查是否有雷的情况时便于查找
#define COLS COL+2///
#define BOOMNUM 10//雷的个数
void Initboard(char board[ROWS][COLS],int rows,int cols,char symbol);
void Displayboard(char board[ROWS][COLS],int row,int col);
void Setmine(char board[ROWS][COLS], int rows, int cols);
void Findmine(char mine[ROWS][COLS],char show[ROWS][COLS],int rows,int cols);
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win);
main.c
#include"game.h"
void menu()
{
printf("*********************************\n");
printf("*********************************\n");
printf("*******1.PLAY 0.EXIT***********\n");
printf("*********************************\n");
printf("*********************************\n");
}
void game()
{
char mine[ROWS][COLS] = {0};//存放布置好类
char show[ROWS][COLS]= {0};存放排查出雷的信息
//mine数组 未布置雷为0
Initboard(mine,ROWS,COLS,'0');
//show数组 没排查为*
Initboard(show,ROWS,COLS,'*');
//设置雷
Setmine(mine,ROW,COL);
Displayboard(show,ROW,COL);
//排查雷
Findmine(mine,show,ROW,COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input =0;
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
switch(input)
{
case 1:
printf("Game start\n");
game();
break;
case 0:
printf("GOODBYE!!!!!\n");
break;
default:
printf("Wrong choice!!!\n");
break;
}
} while (input);
return 0;
}
四、演示
说到这里,c语言扫雷游戏我就介绍完了,希望大家可以有所收获,如果有错误欢迎在评论区留言 !