Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,关注+收藏,欢迎欢迎~~
💥个人主页:小羊在奋斗
💥所属专栏:C语言
本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为同样是初学者的学友展示一些我的学习过程及心得。文笔、排版拙劣,望见谅。
一、扫雷游戏
1、扫雷游戏规则
2、扫雷游戏的实现
3、
一、扫雷游戏
1、扫雷游戏的规则
首先我们来介绍一下扫雷游戏的玩法,扫雷游戏的常规界面(9*9)如下:
上面游戏板上有许多个格子,有些格子里面埋有雷,玩家需要点击格子揭开它们,如果揭开的格子里是雷则被炸死游戏结束,若果揭开的格子不是雷则显示点开的格子周围有多少个雷,玩家需要通过给出的信息进行逻辑判断和猜测来排除所有的雷。
2、扫雷游戏的实现
2.1打印游戏界面
了解完游戏的玩法后,我们就要来好好想想要怎么通过代码来实现这个小游戏。
首先,我们需要新建一个 main.c 文件来存放函数的主体代码,新建一个 game.c 文件用来游戏实现代码,新建一个 game.h 来包含其中会用到的一些头文件和相关函数的声明。
跟其他游戏一样,我们得有个游戏菜单吧,在 —> 猜数字小游戏 这篇文章中我们已经有了一种打印游戏菜单的方法,不妨我们就继续延用这种办法吧。
main.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"//这里我将头文件<stdio.h>包含到game.h,再在main.c和
//game.c文件中包含game.h,避免重复引用
void menu()
{
printf("##########################\n");
printf("######## 1.play ########\n");
printf("######## 0.exit ########\n");
printf("##########################\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)//通过输入的input来选择开始游戏还是退出游戏
{
case 1:
printf("开始游戏!\n");
break;
case 0:
printf("退出游戏!");
break;
default: //如果不小心输入错误的值,还要提示重新选择
printf("选择错误,请重新选择!\n");
break;
}
} while (input);//do—while循环可以帮助我们实现重复玩游戏
return 0;
}
代码运行先打印一个简易的游戏菜单,提示我们选择1开始游戏,选择0退出游戏,选择其他值则提示选择错误,重新选择。当我们选择1开始游戏,游戏结束后通过 break 跳出来到 while 判断,input 的值为1继续循环开始游戏;当我们选择0则退出游戏,通过 break 跳出来到 while 判断,input 的值为0退出循环;当我们选择其他非0的值通过 break 跳出来到 while 判断,非0继续循环。
通过代码执行可以试验出我们当前的逻辑是正确的。我们在写工程量比较大的代码时,写完一段程序最好运行试验一下是否符合我们的想法。
2.2游戏分析
接下来我们就要分析一下该如何实现这个游戏了。首先我们得有一个矩形棋盘吧,这里我们就先设计一个简单的 9*9 游戏棋盘。提到矩形 9*9 棋盘我们就很容易联想到之前学习过的二维数组,二维数组就能很好的帮我们实现这个事情,并且二维数组还能通过坐标唯一确定一个小格子。有了游戏棋盘在玩游戏之前还应该由系统自动布置好雷的位置,并且位置是随机的,影藏在棋盘下我们看不到。
我们设计的这个棋盘不仅要事先随机布置隐藏好雷,还要在我们玩的时候显示周围雷的个数,是不是对这个棋盘要求太高了,我们实现起来也比较复杂。这里我们有一个还不错的解决办法,我们可以定义两个二维数组,一个用来随机产生并且隐藏雷,在我们玩游戏的时候并不打印;另一个在我们玩的时候打印显示排雷的信息也就是周围雷的个数。
提到定义两个二维数组就不得不想清楚我们究竟要定义两个什么类型的二维数组呢?在这之前,我们需要考虑一下怎么区分雷和非雷。其实这一步有很多种方法,想要怎么设计完全由你自己决定,这里我们不妨就定义字符 ‘0’ 为雷,字符 ‘1’ 为非雷吧,至于为什么要定义为字符而不是我们常见的数字1和0,其实是有原因的。
我们前面说过,如果揭开的格子下不是雷,就要将这个格子周围的雷的数目加起来并在我们揭开的这个格子上显示,要显示的话当然显示的是数字,如果这个格子周围恰好是一个雷就要在这个格子上显示数字1,这就非常容易与我们定义的雷(0)或非雷(1)冲突,我们定义为字符的话就可以很好的避免这个问题。
还有一个隐藏的问题,如果我们想排查(8,7)这个坐标,很明显越界了,那我们要判断这个坐标是不是雷之前还要先判断数组是否越界,因为数组越界是比较危险的事情,谁也不知道越界访问到的是什么数据,严重还会导致程序崩溃,所以我们要想办法避免这个问题。
我们可以把之前定义的两个字符型二维数组大小改为 11 行 11 列,而不是用 9 行 9 列,在操作的时候外面一圈不操作,只在 9*9 的棋盘内排雷,这样就不会有越界的问题。
2.3打印游戏棋盘
接上所述,我们定义了两个 11 行 11 列的字符型二维数组,定义好后我们先将埋雷的二维数组初始化为 ‘1’,将显示排雷信息的二维数组初始化为 * ,因为埋雷的二维数组并不打印,所以我们就实现了用一个棋盘覆盖另一个棋盘的效果。
到这里我们先来看一下效果:
测试效果跟我们预期的一样,当然,在真正玩的时候上面埋雷的棋盘是不打印的,这里我们只是测试一下棋盘是否初始化成功。
相关代码如下:
main.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"//这里我将头文件<stdio.h>包含到game.h,再在main.c和
//game.c文件中包含game.h,避免重复引用
void menu()
{
printf("##########################\n");
printf("######## 1.play ########\n");
printf("######## 0.exit ########\n");
printf("##########################\n");
}
void game()
{
//定义两个二维数组作棋盘
char mine[ROWS][COLS] = { 0 };//存放雷
char show[ROWS][COLS] = { 0 };//存放排雷的信息
//初始化二维数组
Init_Board(mine, ROWS, COLS, '1');
Init_Board(show, ROWS, COLS, '*');
//打印棋盘
Display_Board(mine, ROW, COL);
Display_Board(show, ROW, COL);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)//通过输入的input来选择开始游戏还是退出游戏
{
case 1:
game();
break;
case 0:
printf("退出游戏!");
break;
default: //如果不小心输入错误的值,还要提示重新选择
printf("选择错误,请重新选择!\n");
break;
}
} while (input);//do—while循环可以帮助我们实现重复玩游戏
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void Init_Board(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void Display_Board(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("————————扫雷————————\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
game.h
#pragma once
#include <stdio.h>
#define ROW 9 //定义二维数组的行和列方便修改大小
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
//初始化两个二维数组
void Init_board(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void Display_Board(char board[ROWS][COLS], int row, int col);
注意: 虽然我们有些函数操作的是 ROW 和 COL , 但我们函数传参的时候传的还是 ROWS 和 COLS , 因此我们形参接收的时候一定要用 ROWS 和 COLS 接收。
2.4埋雷
我们之前确定了定义 ‘0’ 为雷,下面我们就来探讨如何埋雷。
首先,我们需要确定埋多少个雷,可以定义一个符号常量来选择埋多少个雷,这里就先埋10个雷。其次,埋雷的话肯定是要随机的埋10个雷,那就要产生10个随机的坐标,产生随机数的函数我们在之前的猜数字小游戏中已经使用过,这里就不过多介绍了。(猜数字小游戏)
我们在埋雷的过程中还需要判断这个坐标是否已经埋了雷,这个不难实现,只需要加一个 if 语句即可。我们来看代码实现:
main.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"//这里我将头文件<stdio.h>包含到game.h,再在main.c和
//game.c文件中包含game.h,避免重复引用
void menu()
{
printf("##########################\n");
printf("######## 1.play ########\n");
printf("######## 0.exit ########\n");
printf("##########################\n");
}
void game()
{
//定义两个二维数组作棋盘
char mine[ROWS][COLS] = { 0 };//存放雷
char show[ROWS][COLS] = { 0 };//存放排雷的信息
//初始化二维数组
Init_Board(mine, ROWS, COLS, '1');
Init_Board(show, ROWS, COLS, '*');
//打印棋盘
//Display_Board(mine, ROW, COL);
Display_Board(show, ROW, COL);
//埋雷
Set_Mine(mine, ROW, COL);
Display_Board(mine, ROW, COL);//打印看一下埋雷是否成功
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//调用rand函数之前先要调用srand函数
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)//通过输入的input来选择开始游戏还是退出游戏
{
case 1:
game();
break;
case 0:
printf("退出游戏!");
break;
default: //如果不小心输入错误的值,还要提示重新选择
printf("选择错误,请重新选择!\n");
break;
}
} while (input);//do—while循环可以帮助我们实现重复玩游戏
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void Init_Board(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void Display_Board(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("————————扫雷————————\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void Set_Mine(char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
while (count < EASY_COUNT)
{
x = rand() % row + 1;//产生范围为1-9的随机数
y = rand() % col + 1;
if (mine[x][y] != '0')
{
mine[x][y] = '0';
count++;
}
}
}
game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9 //定义二维数组的行和列方便修改大小
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_COUNT 10
//初始化两个二维数组
void Init_board(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void Display_Board(char board[ROWS][COLS], int row, int col);
//埋雷
void Set_Mine(char mine[ROWS][COLS], int row, int col);
运行结果为:
可以看到,我们已经实现了埋10个雷。
2.5扫雷
接下来就到了最后一步,实现扫雷。
我们需要输入一个坐标,判断此坐标下是否埋着雷,如果是雷则打印 “你踩雷了,游戏失败!”,并且打印出所有雷的位置;如果不是雷则需要在这个坐标处显示周围8个坐标内雷的个数,继续输入坐标扫雷。
怎么获得排查过的坐标周围雷的个数呢?我们不难发现,(x,y)周围8个坐标分别可以表示为x或y加-1、0、1得到的9个坐标,然后把这9个坐标的值分别进去字符 ‘0’,使其转换为整型再加起来,经过处理就能得到雷的个数。
最终的代码为:
main.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"//这里我将头文件<stdio.h>包含到game.h,再在main.c和
//game.c文件中包含game.h,避免重复引用
void menu()
{
printf("##########################\n");
printf("######## 1.play ########\n");
printf("######## 0.exit ########\n");
printf("##########################\n");
}
void game()
{
//定义两个二维数组作棋盘
char mine[ROWS][COLS] = { 0 };//存放雷
char show[ROWS][COLS] = { 0 };//存放排雷的信息
//初始化二维数组
Init_Board(mine, ROWS, COLS, '1');
Init_Board(show, ROWS, COLS, '*');
//打印棋盘
//Display_Board(mine, ROW, COL);
Display_Board(show, ROW, COL);
//埋雷
Set_Mine(mine, ROW, COL);
//Display_Board(mine, ROW, COL);//打印看一下埋雷是否成功
//扫雷
Find_Mine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//调用rand函数之前先要调用srand函数
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)//通过输入的input来选择开始游戏还是退出游戏
{
case 1:
game();
break;
case 0:
printf("退出游戏!");
break;
default: //如果不小心输入错误的值,还要提示重新选择
printf("选择错误,请重新选择!\n");
break;
}
} while (input);//do—while循环可以帮助我们实现重复玩游戏
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void Init_Board(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void Display_Board(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("————————扫雷————————\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void Set_Mine(char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
while (count < EASY_COUNT)
{
x = rand() % row + 1;//产生范围为1-9的随机数
y = rand() % col + 1;
if (mine[x][y] != '0')
{
mine[x][y] = '0';
count++;
}
}
}
int Get_Mine_Count(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int count = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
count += (mine[x + i][y + j] - '0');
}
}
return (9 - count);
}
void Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
while (count < (row * col - EASY_COUNT))//排查出所有的非雷坐标后退出循环
{
printf("请输入想要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//必须输入范围内的坐标
{
if (mine[x][y] == '0')
{
printf("你踩雷了,游戏结束!\n");
Display_Board(mine, ROW, COL);
printf("\n\n\n");
break;
}
else
{
int count = Get_Mine_Count(mine, x, y);
show[x][y] = count + '0';//整型count需要加上字符‘0’才能赋值给字符型数组show
Display_Board(show, ROW, COL);
}
}
else
{
printf("输入的坐标有误,x和y的范围为1-%d\n", row);
}
}
if (count == EASY_COUNT)
{
printf("恭喜你,扫雷成功!\n");
Display_Board(mine, ROW, COL);
}
}
game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9 //定义二维数组的行和列方便修改大小
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_COUNT 10
//初始化两个二维数组
void Init_board(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void Display_Board(char board[ROWS][COLS], int row, int col);
//埋雷
void Set_Mine(char mine[ROWS][COLS], int row, int col);
//扫雷
void Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
至此,我们就完成了整个游戏的实现。
点击跳转主页—> 💥个人主页:小羊在奋斗