一、【扫雷】
《扫雷》是一款经典的单人电脑游戏,旨在揭开一个方块网格,并避免触发隐藏的地雷。每个方块可能包含数字(表示周围地雷数量)或者地雷。玩家需要利用数字信息来推断哪些方块是安全的,并标记可能包含地雷的方块
二、【游戏逻辑】
1.游戏的初始化
扫雷游戏的基础模式是 9×9 的雷区,为在c语言中方便实现,可以用 ' * ' 号表示未翻开的雷区,其中用字符 ' 1 ' 表示地雷,字符 ‘ 0 ’ 表示不存在地雷的区域,并用二维数组进行存储。
2.雷区的翻转
对于雷区的反转,可以通过两张二维表进行实现,分别将雷的信息,排查出雷的信息分别存储到不同二维表中。
在格子未被翻转前,用字符 ‘*’ 来对其进行遮盖,而格子翻转以后,该格子则显示周围的 8 个格子存在的地雷的个数。
3.误区
扫雷游戏规则,规定了,若翻转的格子不是地雷,则显示该格子周围 8 个格子存在地雷的个数,但若选择的是边缘的格子,对雷区边界的格子执行这一操作可能会导致数组的越界访问
为实现 9×9 的扫雷游戏,创建一个9×9的二维数组并不合适,既然对边界元素进行操作时,会导致数组越界访问,那直接将二维数组扩大一圈,使用时,我们继续使用原来的范围,对外圈不进行操作
三、【代码实现】
为提高代码的可阅读性和让代码尽量简洁,便于维护,而分成三个文件。
1.zcx.c
1.1 面板显示
void mb()
{
printf("******************\n");
printf("*****1.开始 1*****\n");
printf("*****2.结束 0*****\n");
printf("******************\n");
}
这个函数用于打印游戏菜单,显示给玩家选择开始游戏或结束游戏
1.2 主函数 main()
int main()
{
int a = 0;
srand((unsigned int)time(NULL));
do {
mb();
printf("请选择:");
scanf("%d", &a);
switch (a)
{
case 1:
printf("-----扫雷开始-----\n");
dt(); // 调用 dt() 函数来执行扫雷游戏逻辑
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (a);
return 0;
}
主函数首先初始化随机数种子,然后进入一个循环中。在每次循环中,程序会打印菜单(使用
mb()
函数),然后等待用户输入选择。根据用户选择不同,程序会执行相应的操作:如果选择开始游戏,则调用dt()
函数;如果选择结束,则打印"游戏结束";如果输入错误,则提示用户重新输入。
1.3 实现扫雷游戏的各功能的引用
//打印扫雷的画面
void dt()
{
char nqb[HANGS][LIES] = { 0 };//存放雷的信息
char wqb[HANGS][LIES] = { 0 };//存放排查出雷的信息显示
//初始化棋盘
csh(nqb, HANGS, LIES, '0');//'0'
csh(wqb, HANGS, LIES, '*');//'*'
//打印棋盘
/*dyqb(wqb, HANG, LIE);*/
//布置雷
bzl(nqb, HANG, LIE);
dyqb(wqb, HANG, LIE);
//排查雷
pcl(nqb, wqb, HANG, LIE);
}
将初始化棋盘需要存储打印的字符,作为实参传递给函数,简化了存储字符的过程。
2.zdy.c
这段代码是扫雷游戏的一部分实现,包括了初始化棋盘、打印棋盘、布置雷、判断周围存在几个雷以及排查雷等功能。
2.1 初始化棋盘函数 csh
void csh(char qqb[HANGS][LIES], int hangs, int lies, char cszf)
{
for (int x = 0; x < HANGS; x++)
{
for (int y = 0; y < LIES; y++)
{
qqb[x][y] = cszf;
}
}
}
这个函数用于初始化棋盘,将所有格子的状态设置为指定的字符。
2.2 打印棋盘函数 dyqb
void dyqb(char wqb[HANGS][LIES], int hang, int lie)
{
// 打印行号
for (int i = 0; i <= HANG; i++)
{
printf("%d", i);
}
printf("\n");
// 打印每行的内容(包括列号和格子状态)
for (int x = 1; x <= HANG; x++)
{
printf("%d", x); // 打印列号
for (int y = 1; y <= LIE; y++)
{
printf("%c", wqb[x][y]); // 打印格子状态
}
printf("\n");
}
}
这个函数用于打印棋盘,显示每个格子的位置,并打印首行首列所对应的数字,方便格子的翻转实现
2.3 布置雷函数 bzl
void bzl(char nqb[HANGS][LIES], int hang, int lie)
{
// 随机布置地雷
int lei = LEI; // 设置地雷数量
srand((unsigned int)time(NULL)); // 使用当前时间作为随机种子
while (lei)
{
int x = rand() % hang + 1; // 随机生成行坐标
int y = rand() % lie + 1; // 随机生成列坐标
if (nqb[x][y] == '0')
{ // 如果该位置没有地雷,则放置地雷并减少剩余地雷数量
nqb[x][y] = '1';
lei--;
}
}
}
- 在循环开始之前,通过
srand((unsigned int)time(NULL))
设置了随机数种子。这样可以确保每次运行程序时都会得到不同的随机序列。- 在循环内部,使用
rand()
函数生成一个0到hang-1
之间的随机整数,并加1得到x坐标;再生成一个0到lie-1
之间的随机整数,并加1得到y坐标。- 然后检查所选位置是否已经有地雷(即nqb[x][y]是否等于'0'),如果是,则将该位置设为地雷('1'),并将剩余地雷数量减一。
2.4 判断周围存在几个雷函数 zwjgl
int zwjgl(char nqb[HANGS][LIES], int x, int y)
{
return nqb[x - 1][y - 1] + nqb[x - 1][y] + nqb[x - 1][y + 1] + nqb[x][y - 1]
+ nqb[x][y + 1] + nqb[x + 1][y - 1] + nqb[x + 1][y] + nqb[x + 1][y + 1] - 8 * '0';
}
- 在这段代码中,通过将当前位置(x,y)及其周围八个格子的值相加,并减去8乘以字符'0'(即48),得到了周围地雷的数量。
- 这是因为在ASCII码中,数字字符'0'到'9'依次对应的ASCII码值是48到57。因此减去8乘以字符'0'实际上就是将字符转换为对应的数字值。
2.5 排查雷函数 pcl
void pcl(char nqb[HANGS][LIES], char wqb[HANGS][LIES], int hang, int lie)
{
int x = 0;
int y = 0;
int w = 0;
while (w < HANG * LIE - LEI) // 当未排查的格子数量小于总格子数减去地雷数量时执行循环
{
printf("请输入排查的坐标:");
scanf("%d %d", &x, &y); // 获取用户输入的坐标
if (x >= 1 && x <= hang && y >= 1 && y <= lie) // 检查输入坐标是否合法
{
if (nqb[x][y] == '1') // 如果踩到地雷,则游戏失败并结束
{
printf("很遗憾,你被炸死了\n");
dyqb(nqb, HANG, LIE); // 展示所有地雷位置
break; // 结束游戏循环
}
else
{
if (wqb[x][y] != '*') // 如果该位置已经排查过,则提示无需再次排查
{
printf("该位置已经排查,无需再次排查\n");
}
else // 否则进行排雷操作,并更新展示给玩家看的数组,并检查是否游戏胜利。
{
int a = zwjgl(nqb, x, y); // 获取周围地雷数量
wqb[x][y] = a + '0'; // 更新展示给玩家看的数组
dyqb(wqb, HANG, LIE); // 打印更新后的棋盘状态(展示给玩家看)
w++; // 增加已排查格子数量计数器
}
}
}
else
{
printf("坐标非法,请重新输入\n"); // 提示用户重新输入合法坐标
}
}
if (w == HANG * LIE - LEI)
{
printf("恭喜你排雷成功\n");
// 如果已经成功排除所有非地雷格子,则输出恭喜信息并展示整个地雷分布情况。
dyqb(nqb, HANG, LIE);
}
}
初始化变量:
- 初始化变量
x
和y
为0,用于接收玩家输入的坐标。- 初始化变量
w
为0,用于计算已经排查过的格子数量。循环排查:
- 使用
while
循环,条件是当未排查的格子数量小于总格子数减去地雷数量时执行循环。- 在循环内部,首先提示用户输入要排查的坐标,并使用
scanf
获取用户输入。检查合法性:
- 检查用户输入坐标是否在合法范围内(即大于等于1且小于等于行数或列数)。
处理用户输入:
- 如果用户踩到地雷,则输出失败信息并展示所有地雷位置,并结束游戏循环。
- 如果该位置已经排查过,则提示无需再次排查;否则进行排雷操作,并更新展示给玩家看的数组,并检查是否游戏胜利。
判断游戏结果:
- 如果已经成功排除所有非地雷格子,则输出恭喜信息并展示整个地雷分布情况。
3.zdy.h
这段代码是一个头文件,其中包含了一些函数的声明和预处理指令。
3.1 常量定义
//方便动态管理行列数
#define HANG 9
#define LIE 9
#define HANGS HANG+2
#define LIES LIE+2
#define LEI 10
这里定义了一些常量,包括棋盘的行数和列数,以及雷的数量,方便动态设置。
3.2 函数声明
//初始化棋盘
void csh(char qqb[HANGS][LIES], int hangs, int lies,char cszf);
//打印棋盘
void dyqb(char wqb[HANGS][LIES], int hang, int lie);
//布置雷
void bzl(char nqb[HANGS][LIES], int hang, int lie);
//排查雷
void pcl(char nqb[HANGS][LIES], char wqb[HANGS][LIES], int hang, int lie);
//判断周围存在几个雷
int zwjgl(char nqb[HANGS][LIES], int x, int y);
这些是函数声明,用于告诉编译器这些函数的存在及其参数类型。在实际的源文件中需要实现这些函数,并且可以使用这个头文件来引入这些函数声明。
四、【完整代码】
1.zcx.c
#define _CRT_SECURE_NO_WARNINGS 1
//扫雷游戏
#include "zdy.h"
//面板
void mb()
{
printf("******************\n");
printf("*****1.开始 1*****\n");
printf("*****2.结束 0*****\n");
printf("******************\n");
}
//打印扫雷的画面
void dt()
{
char nqb[HANGS][LIES] = { 0 };//存放雷的信息
char wqb[HANGS][LIES] = { 0 };//存放排查出雷的信息显示
//初始化棋盘
csh(nqb, HANGS, LIES, '0');//'0'
csh(wqb, HANGS, LIES, '*');//'*'
//打印棋盘
/*dyqb(wqb, HANG, LIE);*/
//布置雷
bzl(nqb, HANG, LIE);
dyqb(wqb, HANG, LIE);
//排查雷
pcl(nqb, wqb, HANG, LIE);
}
int main()
{
int a = 0;
srand((unsigned int)time(NULL));
do {
mb();
printf("请选择:");
scanf("%d", &a);
switch (a)
{
case 1:
printf("-----扫雷开始-----\n");
dt();
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (a);
return 0;
}
2.zdy.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//方便动态管理行列数
#define HANG 9
#define LIE 9
#define HANGS HANG+2
#define LIES LIE+2
#define LEI 10
//初始化棋盘
void csh(char qqb[HANGS][LIES], int hangs, int lies,char cszf);
//打印棋盘
void dyqb(char wqb[HANGS][LIES], int hang, int lie);
//布置雷
void bzl(char nqb[HANGS][LIES], int hang, int lie);
//排查雷
void pcl(char nqb[HANGS][LIES], char wqb[HANGS][LIES], int hang, int lie);
//判断周围存在几个雷
int zwjgl(char nqb[HANGS][LIES], int x, int y);
3.zdy.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "zdy.h"
//初始化棋盘函数
void csh(char qqb[HANGS][LIES], int hangs, int lies, char cszf)
{
for (int x = 0; x < HANGS; x++)
{
for (int y = 0; y < LIES; y++)
{
qqb[x][y] = cszf;
}
}
}
//打印棋盘函数
void dyqb(char wqb[HANGS][LIES], int hang, int lie)
{
for (int i = 0; i <= HANG; i++)
{
printf("%d", i);
}
printf("\n");
for (int x = 1; x <= HANG; x++)
{
printf("%d", x);
for (int y = 1; y <= LIE; y++)
{
printf("%c", wqb[x][y]);
}
printf("\n");
}
}
//布置雷函数
void bzl(char nqb[HANGS][LIES], int hang, int lie)
{
int lei = LEI;
srand((unsigned int)time(NULL));
while (lei) {
int x = rand() % hang + 1;
int y = rand() % lie + 1;
if (nqb[x][y] == '0')
{
nqb[x][y] = '1';
lei--;
}
}
}
//判断周围存在几个雷
int zwjgl(char nqb[HANGS][LIES], int x, int y)
{
return nqb[x - 1][y - 1] + nqb[x - 1][y] + nqb[x - 1][y + 1] + nqb[x][y - 1]
+ nqb[x][y + 1] + nqb[x + 1][y - 1] + nqb[x + 1][y] + nqb[x + 1][y + 1] - 8 * '0';
}
//排查雷函数
void pcl(char nqb[HANGS][LIES], char wqb[HANGS][LIES], int hang, int lie)
{
int x = 0;
int y = 0;
int w = 0;
while (w<HANG*LIE-LEI)
{
printf("请输入排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= hang && y >= 1 && y <= lie)
{
if (nqb[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
dyqb(nqb, HANG, LIE);
break;
}
else
{
if (wqb[x][y] != '*')
{
printf("该位置已经排查,无需再次排查\n");
}
else
{
int a = zwjgl(nqb, x, y);
wqb[x][y] = a + '0';
dyqb(wqb, HANG, LIE);
w++;
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (w == HANG * LIE - LEI)
{
printf("恭喜你排雷成功\n");
dyqb(nqb, HANG, LIE);
}
}