目录
前言
该文章将介绍用c语言编程扫雷游戏的思路,以及如何实现扫雷。
要想实现扫雷游戏,我们需要一定的c语言知识储备,并且对新手而言,耐心与练习是必不可少的。
所以我将尽量把我对扫雷游戏的理解全盘托出。
希望读者能够理解我对扫雷游戏的实现思路。
所需基本知识
变量,常量; 主函数int main();函数printf的使用;函数scanf的使用; 函数的声明与定义;创建函数; 分支语句if(){},if(){}...else...,switch; if语句发嵌套; 循环语句while(),for(;;),do...while(); for的嵌套;#define定义变量; 头文件的创建和使用 ; 复杂过程的简化; 自创函数的命名; return语句的用法;操作符的用法;随机数的生成(rand)与(srand);时间戳time;最重要的二维数组的用法.
扫雷规则以及基本思路
我们所做的扫雷游戏并没有像真正的扫雷游戏一样有漂亮的框架,以及图片;对于我们来说,我们只需要实现它的功能。
让我们看看扫雷的规则是什么:
1.1 扫雷游戏的功能说明
• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦
• 默认随机布置10个雷
• 可以排查雷 ◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
首先我们创建俩个源文件game.c与start.c实现游戏。
因为我们需要很多步骤所以我们创建一个头文件game.h。把函数的调用,头文件的包含都放在game.c中。
步骤1:首先我们需要一个开始的菜单 ,像这样的一个菜单,输入1表示开始游戏,输入0,表示退出游戏。我们将要通过switch语句进行选择,以及do...while语句进行循环选择。
步骤2:接着我们需要一个棋盘来储存我们的数据,如下图,我们用1表示雷,用没有雷就用0表示。
步骤2是关键,也是较难实现的。我们需要创建棋盘,初始化棋盘,打印棋盘,布置雷,排查雷,最后显示成功或失败的信息。
我们所需要的界面如下
接下来我们做具体介绍。
具体步骤与细节
1.菜单与基本的开始或退出游戏
步骤1的实现比较简单,我们先创建菜单函数menu();它的作用是打印菜单;在创建函数时我们需要明确它的返回类型,编辑它的名称,以及它所需要的参数。我们创建的函数叫做自定义函数,如图;
如我们所需要的菜单函数menu()并不需要放回类型,也不需要什么参数,我们就用void表示它不需要返回类型以及参数.
void menu(void)
{
}
void menu(void)
{
printf("#####################\n");
printf("###### 1. play ######\n");
printf("###### 0 .exit ######\n");
printf("#####################\n");
}
有了菜单我们需要主函数来进行选择开始游戏或者退出游戏,如下图:
注意:我们的#include <stdio.h>在我们头文件game.h中,在使用我们创建的头文件我们使用""来引用
#include "game.h"//使用我们创建的头文件,可以减少错误;方便以后修改
void menu(void)
{
printf("#####################\n");
printf("###### 1. play ######\n");
printf("###### 0 .exit ######\n");
printf("#####################\n");
}
int main()
{
//使用switch进行开始游戏或退出游戏
int input = 0;
//使用dowhile循环来进行选择
do {
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();//这是我们要进行的游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("请输入0或1进行退出或开始游戏\n");
break;
}
} while (input);
return 0;
}
将代码中的game()去掉,我们所运行的结果是
如图我们可以一直进行游戏,输入0之后我们退出游戏;
2.游戏game()的实现
首先我们需要分清game有几步,并且分析好我们所要面临的问题。
//创建棋盘//初始化棋盘//打印棋盘//设置雷//排查雷
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些 信息。
因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放 信息。
那如果这个位置布置雷,我们就存放1,没有布置雷就存放0.
2.1创建棋盘
如果我们只创建一个棋盘,在我们设置雷时需要塞入的是'1',在排查雷的时候我们所要排查的也是'1',这会使得冲突所以我们需要俩个棋盘,一个用来显示,一个用来埋雷。
在排查雷的时候我们需要统计我们输入坐标的周围有多少雷('1');如下图我们可以看到在棋盘的边界时没有元素的,如果不加以更改时我们就会造成越界。所以我们在9*9 的基础上,扩张一行一列,即11*11.
game()
{
//创建棋盘,二维数组,俩个数组;一个埋雷,一个展示,避免排查雷和埋雷重复。
//并且在排查雷时我们需要更宽的范围。
char show[ROW][COL];//使用ROW表示行,COL表示列。在game.h里我们定义ROW为9.方便后面直接修改一
//个地方。
char mine[ROW][COL];
}
game()
{
//我们在game.h里定义ROWS为ROW+2;COLS为COL+2;使得我们排查雷时不会越界,同样方便修改
char show[ROWS][COLS];
char mine[ROWS][COLS];
}
头文件game.h里
#include <stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
2.2初始化棋盘
我们创建一个函数专门用来打印方便后面我们调用并且修改,我们创建IntBoard()函数。并且将show,ROW,COL作为参数传给它.
即IntBoard(show,ROWS,COLS);IntBoard(mine,ROWS,COLS)。在使用它时我们需要声明,我们在头文件中给他声明为
void IntBoard(char arr[ROWS][COLS],int rows,int cols);
我们需要将show棋盘初始化为字符'*',将mine棋盘初始化为字符 '0'.要使二维数组的每个元素初始化,我们需要使用俩个循环,一个控制行,一个控制列,嵌套的for循环就很适合.
void IntBoard(char arr[ROWS][COLS], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = ;
}
}
}
哎!这时我们发现我们怎么用一个函数给俩个棋盘初始化,我们是给字符'0'呢还是给字符'1' 呢?
我们发现用一个是不行的,那我们难道用用俩个不同的函数来初始化它们吗?不!我们不需要! 我们需要的只有一个,那就是多一个参数.如下图
在start.c中我们将'0'与'1'传给函数IntBoard();即
IntBoard(show, ROWS, COLS,'*');
IntBoard(mine, ROWS, COLS,'0');
在game.c中,我们将这个参数的形参设为set
void IntBoard(char arr[ROWS][COLS], int rows, int cols,int set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
这时我们的初始化就大功造成.在写好每个函数我们可以调试一下,避免发生基础的错误.
2.3打印棋盘
和初始化棋盘一样我们同样需要创建函数来专门用来打印棋盘。我们将函数名命名为DisplayBoard();将参数传给函数DisplayBoard(show,ROW,COL);因为我们只需要打印9*9的格子所以我们将ROW(9),COL(9)传给函数。在game.h中声明函数void DisplayBoard(char arr[ROWS][COLS],int row,int col); 接着在game.c中我们实现函数DisplayBoard(show,ROW,COL)。
怎么实现,我们要想打印9*9 的格子,它要有行有列,我们最先想到的就是俩个for嵌套,如下
//有人会对为什么我们要将arr的行列创建成ROWS,COLS。
// //这是因为我们在创建show数组的时候是用ROWS,COLS 创建的。
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
int i = 0;//控制行数
int j = 0;//控制列数
//下标为1的行数开始表示第一列,
//因为我们使用的show数组是初始化为11*11的格子的
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)//下标为1开始的列数。这样在后面我们输入坐标的时候就不会错误
{
printf("%c ", arr[i][j]);
}
printf("\n");//一行完成我们需要回车。
}
}
运行的结果是
这样我们就打印成功了,但是没有坐标怎么办,所以我们需要在上面的代码中修改.
//有人会对为什么我们要将arr的行列创建成ROWS,COLS。
//这是因为我们在创建show数组的时候是用ROWS,COLS 创建的。
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
int i = 0;//控制行数
int j = 0;//控制列数
//使用for循环打印出纵坐标
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");//注意换行
//下标为1的行数开始表示第一列,
//因为我们使用的show数组是初始化为11*11的格子的
for (i = 1; i <= row; i++)
{
printf("%d ", i);//在每行的前面加上坐标1-9
for (j = 1; j <= col; j++)//下标为1开始的列数。这样在后面我们输入坐标的时候就不会错误
{
printf("%c ", arr[i][j]);
}
printf("\n");//一行完成我们需要回车。
}
}
它运行的结果是:
如此我们的棋盘打印就成功了。
2.4设置雷
同上面一样,我们将该函数命名为Set_Mine();将参数传给它 Set_Mine(mine,ROW,COL); 并且在game.h中进行声明void Set_Mine(char arr[ROWS][COLS],int row,int col);
我们将要在9*9的mine数组棋盘里设置雷,字符'1'表示雷,字符'0'不是雷,那我们该如何实现。首先我们需要一个随机的坐标[x][y]埋雷,做到随机那我们就需要用到随机数,接下来我们介绍如何产生随机数。我们需要用到一个函数rand();同时需要时间戳time与srand来使得rand是随机的。如下:
rand需要<stdlib.h>头文件 time需要<time.h>头文件
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
srand((unsigned int)time(NULL));
int x = rand();
int y = rand();
这个解释起来比较复杂,既然记住不难那我们就记住这个格式。
虽然我们产生了随机数,但是这个随机数不能超出范围。所以我们给它取模(就是除以一个数后它的余数)%.将它限定到1-9范围内
int x = rand()%9+1;//因为任意的一个正整数它取模后都是0-8;则加1就是1-9;
int y = rand()%9+1;
当然我们需要规定雷的数量我们定义一个整形mine_num表示雷的数量,并在game.h中给它定值,要埋雷十次就是需要循环十次.我们用while来进行循环埋雷。如下
//game.h中
#define mine_num 10
void Set_Mine(char arr[ROWS][COLS], int rows, int cols);
//game.c中
void Set_Mine(char arr[ROWS][COLS], int row, int col)
{
int count = mine_num;//雷的数量
while (count)
{
int x = rand() % row + 1;//任何一个数取9的余数是0-8加一就是1-9;
//即在11*11的格子里我们将下标为1-9的元素塞进雷“1”
int y = rand() % col + 1;//使用rand我们需要srand来进随机
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
有十个雷就循环十次。依次我们总结一下我们的代码,稍后的排查雷是游戏实现中最难的。
到现在。
//game.h
#pragma once
#include <stdio.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define mine_num 10
void IntBoard(char arr[ROWS][COLS], int rows, int cols);
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
void Set_Mine(char arr[ROWS][COLS], int row, int col);
//game.c
#define _CRT_SECURE_NO_WARNINGS 1//vs中的警报,使得scanf正常使用
#include "game.h"
void IntBoard(char arr[ROWS][COLS], int rows, int cols,int set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
//有人会对为什么我们要将arr的行列创建成ROWS,COLS。
//这是因为我们在创建show数组的时候是用ROWS,COLS 创建的。
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
int i = 0;//控制行数
int j = 0;//控制列数
//使用for循环打印出纵坐标
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");//注意换行
//下标为1的行数开始表示第一列,
//因为我们使用的show数组是初始化为11*11的格子的
for (i = 1; i <= row; i++)
{
printf("%d ", i);//在每行的前面加上坐标1-9
for (j = 1; j <= col; j++)//下标为1开始的列数。这样在后面我们输入坐标的时候就不会错误
{
printf("%c ", arr[i][j]);
}
printf("\n");//一行完成我们需要回车。
}
}
void Set_Mine(char arr[ROWS][COLS], int row, int col)
{
int count = mine_num;//雷的数量
while (count)
{
int x = rand() % row + 1;//任何一个数取9的余数是0-8加一就是1-9;
//即在11*11的格子里我们将下标为1-9的元素塞进雷“1”
int y = rand() % col + 1;//使用rand我们需要srand来进随机
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
//start.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"//使用我们创建的头文件,可以减少错误;方便以后修改
void menu(void)
{
printf("#####################\n");
printf("###### 1. play ######\n");
printf("###### 0 .exit ######\n");
printf("#####################\n");
}
void game(void)
{
//我们在game.h里定义ROWS为ROW+2;COLS为COL+2;使得我们排查雷时不会越界,同样方便修改
char show[ROWS][COLS];
char mine[ROWS][COLS];
//初始化棋盘
IntBoard(show, ROWS, COLS, '*');
IntBoard(mine, ROWS, COLS, '0');
//打印棋盘
DisplayBoard(show, ROW, COL);
/*DisplayBoard(mine, ROW, COL);*/
//设置雷
Set_Mine(mine,ROW,COL);
}
int main()
{
//使用switch进行开始游戏或退出游戏
int input = 0;
//使用dowhile循环来进行选择
do {
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();//这是我们要进行的游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("请输入0或1进行退出或开始游戏\n");
break;
}
} while (input);
return 0;
}
我们使用打印函数来看我们的雷是否埋好,即在设置雷下使用打印函数。
至此我们完成了设置雷。
2.5排查雷*(此步骤比较复杂建议多加练习)
同样先取名Fine_Mine();传参Fine_Mine(show,mine,ROL,COL);再声明
void Fine_Mine(char show[ROWS][COLS], char mine[ROS][COLS],int row, int col);
接下来实现排查。
我们是通过输入坐标来排查雷的,因此创建变量x,y表示坐标。整理一下思路,首先输入坐标(scanf),然后对坐标进行判断(if),如果我们输入的坐标是正确的,则我们猜对一次,但是我们需要猜完所有的不是雷的坐标,因此我们创建一个变量win表示我们猜对的次数。
在猜对的同时我们还要计算周围的雷的数目*。为了方便我们创建一个函数来专门计算雷的数目Get_Mine(mine,x,y).计算完周围有多少雷之后打印我们猜之后的棋盘show。
void Fine_Mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
//循环输入,猜错即跳出循环
while(win<row*col-mine_num) //当win小于成功次数,即9*9-10雷数
{
printf("请输入你要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y>=1 && y < col)//坐标正确才能进行判断
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
break;//跳出循环
}
else
{
//如果不是雷就统计周围的雷的数量
int count = Get_Mine(mine,x,y);
show[x][y] = Get_Mine(mine,x,y);/将周围的雷的数目赋值给show
DisplayBoard(show, ROW, COL);//打印猜对之后的棋盘进行下一次猜
win++;
}
}
if(win == row*col - mine_num)
{
printf("恭喜你!排雷成功!\n");
}
}
下面我们进行周围雷的数目怎么计算.
首先我们知道我们使用的是字符'1'做为雷,字符'0'做为不是雷,这时我们应该想到ASCII
发现数字字符是连续的,因此我们可以知道,当'1' - '0' = 数字 1,依据此,同样字符'2' - '0' = 2;
给出下面的代码,设我们排查的坐标x,y。返回类型是int,给我们的函数在,在show = Get_Mine(mine,x,y) + '0' 就是我们周围的雷的数目。(因为show是char类型所以我们需要加上字符'0')
int Get_Mine(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] +
mine[x + 1][y] - 8 * '0';//如果一个雷没有则是8个’0‘,则值为0;
//如果有一个则有一个字符’1‘加起来之后减8*’0‘就是1;
}
再给出完整的代码
int Get_Mine(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] +
mine[x + 1][y] - 8 * '0';//如果一个雷没有则是8个’0‘,则值为0;
//如果有一个则有一个字符’1‘加起来之后减8*’0‘就是1;
}
void Fine_Mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - mine_num)
{
printf("请输入你要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y < col)
{
if (mine[x][y] == '1')
{
//如果是雷就跳出循环
printf("你被炸死了\n");
break;
}
else
{
//如果不是雷就统计周围的雷的数量
int count = Get_Mine(mine, x, y);
show[x][y] = Get_Mine(mine, x, y) + '0';//Get_Mine 的返回类型为字符,则如果周围有n个雷,
//加上字符'0'就是字符‘1’。。。。。
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法\n");
}
}
if (win == row * col - mine_num)
{
printf("恭喜你排雷成功\n");
}
}
至此我们完成了排查雷的步骤。当然我们需要测试。但是我们要排查71次测试耗时太久,所以我们将雷改成80个测试排雷结束是成功.在头文件中我们所定义的雷的数目
结尾
我们给出完整代码,首先头文件game.h。
#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 mine_num 80 void IntBoard(char arr[ROWS][COLS], int rows, int cols); void DisplayBoard(char arr[ROWS][COLS], int row, int col); void Set_Mine(char arr[ROWS][COLS], int rows, int cols); void Fine_Mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void IntBoard(char arr[ROWS][COLS], int rows, int cols,int set) { int i = 0; int j = 0; for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { arr[i][j] = set; } } } //有人会对为什么我们要将arr的行列创建成ROWS,COLS。 //这是因为我们在创建show数组的时候是用ROWS,COLS 创建的。 void DisplayBoard(char arr[ROWS][COLS], int row, int col) { int i = 0;//控制行数 int j = 0;//控制列数 //使用for循环打印出纵坐标 for (i = 0; i <= col; i++) { printf("%d ", i); } printf("\n");//注意换行 //下标为1的行数开始表示第一列, //因为我们使用的show数组是初始化为11*11的格子的 for (i = 1; i <= row; i++) { printf("%d ", i);//在每行的前面加上坐标1-9 for (j = 1; j <= col; j++)//下标为1开始的列数。这样在后面我们输入坐标的时候就不会错误 { printf("%c ", arr[i][j]); } printf("\n");//一行完成我们需要回车。 } } void Set_Mine(char arr[ROWS][COLS], int row, int col) { int count = mine_num;//雷的数量 while (count) { int x = rand() % row + 1;//任何一个数取9的余数是0-8加一就是1-9; //即在11*11的格子里我们将下标为1-9的元素塞进雷“1” int y = rand() % col + 1;//使用rand我们需要srand来进随机 if (arr[x][y] == '0') { arr[x][y] = '1'; count--; } } } int Get_Mine(char mine[ROWS][COLS], int x, int y) { return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y] - 8 * '0';//如果一个雷没有则是8个’0‘,则值为0; //如果有一个则有一个字符’1‘加起来之后减8*’0‘就是1; } void Fine_Mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int win = 0; while (win < row * col - mine_num) { printf("请输入你要排查的坐标:"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y < col) { if (mine[x][y] == '1') { //如果是雷就跳出循环 printf("你被炸死了\n"); break; } else { //如果不是雷就统计周围的雷的数量 int count = Get_Mine(mine, x, y); show[x][y] = Get_Mine(mine, x, y) + '0';//Get_Mine 的返回类型为字符,则如果周围有n个雷, //加上字符'0'就是字符‘1’。。。。。 DisplayBoard(show, ROW, COL); win++; } } else { printf("坐标非法\n"); } } if (win == row * col - mine_num) { printf("恭喜你排雷成功\n"); } }
start.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h"//使用我们创建的头文件,可以减少错误;方便以后修改 void menu(void) { printf("#####################\n"); printf("###### 1. play ######\n"); printf("###### 0 .exit ######\n"); printf("#####################\n"); } void game(void) { //我们在game.h里定义ROWS为ROW+2;COLS为COL+2;使得我们排查雷时不会越界,同样方便修改 char show[ROWS][COLS]; char mine[ROWS][COLS]; //初始化棋盘 IntBoard(show, ROWS, COLS, '*'); IntBoard(mine, ROWS, COLS, '0'); //打印棋盘 DisplayBoard(show, ROW, COL); /*DisplayBoard(mine, ROW, COL);*/ //设置雷 Set_Mine(mine,ROW,COL); /* DisplayBoard(mine, ROW, COL);*///用于打印出雷的位置看是否布置好雷 //排查雷 Fine_Mine(show, mine, ROW, COL); } int main() { //使用switch进行开始游戏或退出游戏 int input = 0; //使用dowhile循环来进行选择 do { menu(); scanf("%d", &input); switch (input) { case 1: printf("开始游戏\n"); game();//这是我们要进行的游戏 break; case 0: printf("退出游戏\n"); break; default: printf("请输入0或1进行退出或开始游戏\n"); break; } } while (input); return 0; }
如果你有所收获可以留下你的点赞和关注,谢谢你的观看!