目录
1.前言
2.完整流程
2.1规划书
2.2代码部分
2.2.1文件的结构设计
2.2.2变量的创建
2.2.3菜单的基本实现
2.2.4初始化期棋盘
2.2.5输出完整棋盘
2.2.6埋雷的实现
2.2.7查询周围雷的数量
2.2.8扫雷的实现
2.2.9完整代码
3.总结
1.前言
哈喽大家好吖,今天笔者就前期基本所学手搓了一个扫雷的小项目,考察的知识点很简单仅有基本的分支,循环,函数调用,数组的基本应用,相信你也可以做到滴,快来试试喔~
2.完整流程
2.1规划书
虽然完成扫雷的代码量并不大,但我们还是要理清楚我们需要完成的功能和运行的基本逻辑。
这是一张扫雷的基本界面,通过观察和实际游玩可分析得出:
基本功能包括:
- 游戏可以通过菜单实现继续玩或者退出游戏
- 扫雷的棋盘是9*9的格⼦
- 默认随机布置10个雷
- 可以排查雷
- 如果位置不是雷,就显⽰周围有⼏个雷
- 如果位置是雷,就炸死游戏结束
- 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
在梳理完需要实现的功能以后,就可以开始逐步实现以上功能咯。(当然笔者目前无法做出上述照片如此精良的扫雷,只能做一个简略版滴~)
2.2代码部分
2.2.1文件的结构设计
扫雷的代码量虽然不大,但是已经不适合用单文件来完成了,于是乎我们就把,游戏逻辑的基本实现,相关游戏函数的声明,以及游戏相关函数的实现分别打包,方便管理。
我们创建以下三个文件:
test.c //游戏的测试逻辑
game.c //游戏中函数的实现
game.h //游戏需要的数据类型和函数声明等
完成后是这个样子。
另外,在正式敲代码前,我们应先在game.h这个头文件下加上
#pragma once
#pragma once 是一个预处理指令,用来防止头文件的内容在一个编译单元中被多次包含。这与传统的头文件保护(也称为包含保护或头文件守卫)非常相似,后者通常使用#ifndef, #define, 和 #endif来实现。
2.2.2变量的创建
在扫雷这个游戏中,我们一共需要用到以下几个变量:
- 棋盘一(origin)用于记录是否埋雷
- 棋盘二(infor)用于记录雷的相关信息
- 常量row,col分别用于记录扫雷中最核心的9*9区域
- 常量rows,cols用于记录大棋盘11*11区域
- 常量easy用于记录所要标记的雷
实现分别如下(常量记得要在head.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 10//记录简单难度有十个雷
俩个棋盘的创建我放在了game函数中。
void game() {
char origin[rows][cols];
char infor[rows][cols];//创建俩个棋盘
//初始化棋盘
init(origin, rows, cols, '0');
init(infor, rows, cols, '*');
//打印棋盘用于调试
print(infor, row, col);
//开始埋雷
setboom(origin, row, col);
//print(origin, row, col);
//开始排雷
findboom(origin, infor, row, col);
}
2.2.3菜单的基本实现
为了菜单的基本美观,我们在实现的时候可以进行反复调试,代码如下:
void menu() {
printf("------------------------\n");
printf("------欢迎来到扫雷------\n");
printf("------------------------\n");
printf("----------菜单----------\n");
printf("-------1.开始游戏-------\n");
printf("-------2.结束游戏-------\n");//基础菜单实现
printf("---------3.彩蛋---------\n");
return;
}
int main() {
int n = 0;//创建选项变量
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择选项\n");
scanf("%d", &n);
switch (n) {
case 1:game(); break;
case 2:printf("已退出\n"); n = 0; break;
case 3:printf("彩蛋\n"); n = 0; break;
default:printf("请重新输入\n");
}
} while (n);//do while的巧妙利用
return 0;
}
菜单的基本功能的实现是通过switch语句, 如果要玩游戏输入1进入game函数,如果选择退出或者彩蛋选项,将会在case语句中将n置为0从而跳出do while语句,最终实现是这个样子:
2.2.4初始化期棋盘
从这一部分往下,就是开始要分别写出实现每个小功能的函数,记得在game.c中写完函数要在game.h完成声明。
对于初始化,我们将origin这个棋盘全部置为字符'0',将infor这个棋盘全部置为'*'。
game.c中:
void init(char board[rows][cols], int rows1, int cols1, char set) {
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
函数的声明我在这就不完成了,就是一个小小体力活, 最后完整代码会体现出来。效果如下:
2.2.5输出完整棋盘
输出的时候,除了将9*9的核心区域打印出来,还要记得标记行列(1~9)以及x,y轴,增加可玩性。
void print(char board[rows][cols], int row1, int col1) {
printf("-----蘸豆---爽-----\n");
printf("0 1 2 3 4 5 6 7 8 9 y\n");
for (int i = 1; i <= row1; i++) {
printf("%d ", i);
for (int j = 1; j <= col1; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("x\n");
}
最终效果如下:
2.2.6埋雷的实现
由于埋雷的随机性,这里需要一个c语言中的随机数函数rand。
rand用法:
在C语言中,
rand()
是一个标准库函数,用于生成一个伪随机数。这个函数定义在<stdlib.h>
头文件中。rand()
函数返回一个在0
到RAND_MAX
之间的整数,其中RAND_MAX
是在<stdlib.h>
中定义的一个宏,其值至少为32767
。由于
rand()
生成的是伪随机数,因此在每次程序运行时,如果不进行任何设置,它将生成相同的随机数序列。为了得到不同的随机数序列,通常在程序开始时使用srand()
函数来设置随机数生成器的种子。种子的值通常来自于系统时间或者其他变化的数据。这里就是用时间作为种子:srand((unsigned int)time(NULL)); (在主函数中书写了出来)
埋雷的时候我们使用while循环进行操作,另外有一个小小的注意点,如果该点已经被埋过雷了,注意排除,代码如下:
void setboom(char board[rows][cols], int row1, int col1) {
int count = easy;//记录雷数
while (count) {
int x = rand() % 9 +1;
int y = rand() % 9 +1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
如果该位置为'0'就进行埋雷操作,如果为'1'就重复该循环。
2.2.7查询周围雷的数量
在正式开始扫雷函数的实现前,我们还需要先实现查找以该点为中心(非雷)周围八个格子雷的数量。实现这个功能有很多方式,这我采用了ascii码的一些基本特性。
int set_infor(char board[rows][cols], int x, int y) {
return (board[x - 1][y - 1] + board[x][y - 1] +
board[x - 1][y] + board[x][y + 1]
+ board[x + 1][y - 1] + board[x + 1][y]
+ board[x + 1][y + 1] + board[x - 1][y + 1] - 8 * '0');
}
因为我创建的俩个棋盘都是char类型,所以说这个方法就可以使用。
2.2.8扫雷的实现
这块部分比较核心,以下是思路讲解:
- 创建下x,y变量用于记录坐标,创建win用于标记当前排除了多少个区域。
- 主体函数为while循环,循环条件为win变量仍然小于9*9总格数减去总雷数,那么胜利条件自然是win变量等于9*9总格数减去总雷数。
- 接下来讲解while主体里面思路:
- 先让玩家输入x,y坐标,并判断该坐标是否为坐标上的合法目标,如果不是就让玩家重新输入,如果该坐标合法就进行下一步。
- 判断如果玩家选择的坐标为雷,则该玩家被炸死并跳出循环。
- 如果选择的非雷,就打印出该位置上周围八个格子的雷数。
最终代码如下:
void findboom(char origin1[rows][cols], char infor1[rows][cols], int row1, int col1) {
int x = 0;
int y = 0;
int win = 0;//用于记录当前排掉了多少个雷
while (win < row1 * col1 - easy) {
printf("请输入你要排查的x坐标>__");
scanf("%d", &x);
printf("请输入你要排查的y坐标>__");
scanf("%d", &y);
if (x > 0 && y > 0 && x <= row1 && y <= col1) {
if (origin1[x][y] == '1') {
printf("你个憨批你被炸死了\n");
break;
}
else {
int num = set_infor(origin1, x, y);
infor1[x][y] = num + '0';
print(infor1, col1, row1);
win++;
}
}
else {
printf("该坐标不在棋盘上,你这个笨蛋\n请重新输入\n");
}
}
if (win == row1 * col1 - easy) {
printf("恭喜你!你胜利咯\n");
print(origin1, row1, col1);
}
}
运行如下图:
2.2.9完整代码
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 10//记录简单难度有十个雷
void menu();
void init(char board[rows][cols], int rows1, int cols1, char set);
void setboom(char board[rows][cols], int row1, int col1);
void print(char board[rows][cols], int row1, int col1);
int set_infor(char board[rows][cols], int x, int y);
void findboom(char origin1[rows],char infor1[rows][cols],int row1,int col1);
game.c:
#define _CRT_SECURE_NO_WARNINGS 1
//相关函数的定义
#include"game.h"
void menu() {
printf("------------------------\n");
printf("------欢迎来到扫雷------\n");
printf("------------------------\n");
printf("----------菜单----------\n");
printf("-------1.开始游戏-------\n");
printf("-------2.结束游戏-------\n");//基础菜单实现
printf("---------3.彩蛋---------\n");
return;
}
void init(char board[rows][cols], int rows1, int cols1, 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 print(char board[rows][cols], int row1, int col1) {
printf("-----蘸豆---爽-----\n");
printf("0 1 2 3 4 5 6 7 8 9 y\n");
for (int i = 1; i <= row1; i++) {
printf("%d ", i);
for (int j = 1; j <= col1; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("x\n");
}
void setboom(char board[rows][cols], int row1, int col1) {
int count = easy;//记录雷数
while (count) {
int x = rand() % 9 +1;
int y = rand() % 9 +1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
int set_infor(char board[rows][cols], int x, int y) {
return (board[x - 1][y - 1] + board[x][y - 1] +
board[x - 1][y] + board[x][y + 1]
+ board[x + 1][y - 1] + board[x + 1][y]
+ board[x + 1][y + 1] + board[x - 1][y + 1] - 8 * '0');
}
void findboom(char origin1[rows][cols], char infor1[rows][cols], int row1, int col1) {
int x = 0;
int y = 0;
int win = 0;//用于记录当前排掉了多少个雷
while (win < row1 * col1 - easy) {
printf("请输入你要排查的x坐标>__");
scanf("%d", &x);
printf("请输入你要排查的y坐标>__");
scanf("%d", &y);
if (x > 0 && y > 0 && x <= row1 && y <= col1) {
if (origin1[x][y] == '1') {
printf("你个憨批你被炸死了\n");
break;
}
else {
int num = set_infor(origin1, x, y);
infor1[x][y] = num + '0';
print(infor1, col1, row1);
win++;
}
}
else {
printf("该坐标不在棋盘上,你这个笨蛋\n请重新输入\n");
}
}
if (win == row1 * col1 - easy) {
printf("恭喜你!你胜利咯\n");
print(origin1, row1, col1);
}
}
test.c:
#define _CRT_SECURE_NO_WARNINGS 1//游戏逻辑的实现
#include"game.h"
void game() {
char origin[rows][cols];
char infor[rows][cols];//创建俩个棋盘
//初始化棋盘
init(origin, rows, cols, '0');
init(infor, rows, cols, '*');
//打印棋盘用于调试
print(infor, row, col);
//开始埋雷
setboom(origin, row, col);
//print(origin, row, col);
//开始排雷
findboom(origin, infor, row, col);
}
int main() {
int n = 0;//创建选项变量
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择选项\n");
scanf("%d", &n);
switch (n) {
case 1:game(); break;
case 2:printf("已退出\n"); n = 0; break;
case 3:printf("彩蛋是这个~\n"); n = 0; break;
default:printf("请重新输入\n");
}
} while (n);//do while的巧妙利用
return 0;
}
3.总结
以上就是扫雷的基本功能实现了,其实还有许多可以扩展,例如点开一个非雷区自动展开周围其他非雷区域,调整难度等,大家都可以自己尝试尝试哦~。都看到这里了,希望大家对我多多支持喔,大家一起进步~