C语言实现经典扫雷游戏:从零构建完整逻辑与代码详解
本文详细介绍如何使用C语言开发控制台版扫雷游戏,涵盖从模块拆分、核心算法(布雷、排雷、展开)到完整代码实现的每一步骤。
游戏架构与核心模块
扫雷游戏的编写需要清晰的逻辑结构。在开始编码前,需明确程序包含的功能模块,并通过主函数将这些模块串联。一个完整的C语言扫雷程序通常包含以下核心功能:
- 菜单与交互:启动界面及选项选择。
- 棋盘初始化:准备两张棋盘,一张展示给玩家(显示
*),一张记录真实雷位信息。 - 布雷逻辑:在指定范围内随机生成固定数量的地雷。
- 安全检查:确保玩家第一次点击不会踩雷。
- 统计与展开:计算坐标周围8个邻居的雷数;若周围无雷,则自动展开相邻空白区域。
- 胜负判断:当非雷区域全部被揭开时获胜,踩雷则失败。
第一步:定义头文件与全局变量
为了封装游戏逻辑,首先创建头文件game.h。这里定义了棋盘的行(row)和列(col)以及雷的总数(COUNT),并声明了外部使用的两个二维数组:show_mine用于展示给玩家的界面,real_mine用于存储雷的实际分布位置。同时列出所有功能函数的原型。
#ifndef __GAME_H__
#define __GAME_H__
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// 定义棋盘大小和雷的数量
#define row 12 // 实际有效游戏区域为中间的10x10
#define col 12
#define COUNT 10
extern char show_mine[row][col]; // 展示数组,初始化为'*'
extern char real_mine[row][col]; // 布雷数组,'0'表示无雷,'1'表示有雷
void muen(); // 菜单函数
void init_mine(); // 初始化数组
void set_mine(); // 布雷函数
int count_mine(int x, int y); // 统计周围雷的个数
void print_player(); // 打印玩家棋盘
void print_mine(); // 打印设计者棋盘(调试用)
int sweep_mine(); // 扫雷函数
void safe_mine(); // 避免第一次被雷炸死
void open_mine(int x, int y);// 展开函数(简化版,针对直接相邻的8格)
int count_show_mine(); // 判断玩家棋盘剩余未知区域的个数
#endif //__GAME_H__
第二步:实现主函数与游戏循环
主函数负责生成随机种子、调用菜单并处理用户输入。游戏核心逻辑封装在game()函数中。流程如下:
- 初始化:清空并初始化两张棋盘。
- 布雷:随机放置地雷。
- 首步安全:调用
safe_mine处理第一次落子,若踩雷则移动雷的位置并确保当前点周围无雷。 - 循环扫雷:获取用户坐标,检查是否胜利或失败,更新棋盘状态。
#include "game.h"
void game()
{
init_mine(); // 初始化玩家棋盘和设计者棋盘
set_mine(); // 给设计者棋盘布雷
// 可选:打印设计者棋盘查看雷的分布进行调试
// print_mine();
printf("\n");
print_player(); // 打印玩家棋盘
safe_mine(); // 确保第一次不被炸死
// 检查一步胜利的特殊情况
if (count_show_mine() == COUNT)
{
print_mine();
printf("玩家赢!\n\n");
return;
}
print_player();
while (1) // 循环扫雷
{
int ret = sweep_mine(); // 执行扫雷动作,踩到雷返回1,未踩到返回0
// 判断胜利条件:当剩余未打开的非雷格子数量等于总雷数时
if (count_show_mine() == COUNT)
{
print_mine();
printf("玩家赢!\n\n");
break;
}
if (ret) // 如果返回1,说明踩雷
{
printf("被雷炸死\n");
print_mine(); // 展示雷阵位置
break;
}
print_player(); // 打印最新状态的棋盘
}
}
int main()
{
srand((unsigned int)time(NULL)); // 初始化随机数生成器
int input = 0;
do
{
muen(); // 显示菜单
scanf_s("%d", &input); // 读取输入
switch (input)
{
case 1:
game(); // 开始游戏
break;
case 0:
exit(1); // 退出程序
break;
default:
printf("输入错误,重新输入\n");
break;
}
printf("Continue? (Y/N): ");
// 注意:原文此处逻辑简单,实际项目中建议增加更严谨的回车符清理和继续判定
} while (1);
return 0;
}
第三步:实现核心功能函数
以下是各模块的具体实现细节。代码中使用了scanf_s以适应部分编译器环境,标准C也可使用scanf。
1. 初始化与打印
init_mine将展示数组填充为'*',将真实数组填充为'0'。打印函数需注意坐标轴的对齐,通常在中间10x10的有效区域外保留一圈边界,以便后续处理边界坐标溢出问题(虽然本示例主要通过输入判断处理边界)。
2. 布雷函数 set_mine
利用rand() % 10 + 1生成1-10之间的随机坐标。当目标位置尚未埋雷时(值为'0'),将其设为'1',并递减计数,直到埋够COUNT个雷。
3. 统计周围雷数 count_mine
给定坐标(x, y),遍历其周围8个邻居(即x-1~x+1, y-1~y+1)。统计其中值为'1'的数量。这是扫雷显示数字的核心逻辑。
4. 首次安全策略 safe_mine
此函数处理第一次输入。若玩家第一次就踩到雷(real_mine[x][y] == '1'),程序会做两件事:
- 将当前雷移除,改为
'0'。 - 在当前点周围(及该点本身)计算周围雷数并填入
show_mine。 - 在其他任意空白位置新布一枚雷,保证总雷数不变且不影响当前局面的公平性。
这样确保了“第一次绝不死”的用户体验。
5. 扫雷与展开 sweep_mine 和 open_mine
sweep_mine接收坐标,若为安全区,则调用count_mine将结果转为字符存入show_mine,并调用open_mine。在此简化版本中,open_mine仅处理直接相邻的8个格子(非全递归泛洪填充),若周围为'0'则展开显示,否则保留数字。更完善的递归展开通常需要单独的DFS或BFS函数,但本例展示了基本的展开概念。
6. 胜利判断 count_show_mine
遍历展示数组,统计剩余仍为'*'的数量。当这个数量恰好等于总雷数时,意味着所有非雷格子都已被翻开,玩家获胜。
完整源代码汇总
将上述逻辑整合,以下是可编译运行的完整.c文件代码结构(假设头文件已按上文定义):
#include "game.h"
#include <time.h>
char show_mine[row][col] = { 0 };
char real_mine[row][col] = { 0 };
void muen()
{
printf("*******************************\n");
printf("*****1.Play 0.Exit****\n");
printf("*******************************\n");
}
void init_mine()
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
show_mine[i][j] = '*';
real_mine[i][j] = '0';
}
}
}
void print_player()
{
// 省略具体打印逻辑以节省篇幅,参考上文step 2代码中的print_player实现
int i, j;
printf(" ");
for (i = 1; i < row - 1; i++) printf("%d ", i);
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%c ", show_mine[i][j]);
}
printf("\n");
}
}
void print_mine()
{
// 类似print_player,但输出real_mine
int i, j;
printf("\n--- Mine Map ---\n");
printf(" ");
for (i = 1; i < row - 1; i++) printf("%d ", i);
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%c ", real_mine[i][j]);
}
printf("\n");
}
}
void set_mine()
{
int x, y, count = COUNT;
while (count)
{
x = rand() % 10 + 1;
y = rand() % 10 + 1;
if (real_mine[x][y] == '0')
{
real_mine[x][y] = '1';
count--;
}
}
}
int count_mine(int x, int y)
{
int c = 0;
// 检查周围8个位置
if (real_mine[x-1][y-1] == '1') c++;
if (real_mine[x-1][y] == '1') c++;
if (real_mine[x-1][y+1] == '1') c++;
if (real_mine[x][y-1] == '1') c++;
if (real_mine[x][y+1] == '1') c++;
if (real_mine[x+1][y-1] == '1') c++;
if (real_mine[x+1][y] == '1') c++;
if (real_mine[x+1][y+1] == '1') c++;
return c;
}
void safe_mine()
{
int x, y;
printf("Input first coordinate: ");
while (1)
{
scanf_s("%d%d", &x, &y);
if (x >=1 && x <=10 && y >=1 && y <=10)
{
if (real_mine[x][y] == '1')
{
// 补救:移走当前雷
real_mine[x][y] = '0';
// 计算周围雷数并填入show_mine
char val = count_mine(x, y);
show_mine[x][y] = val + '0';
open_mine(x, y);
// 在其他地方补一颗雷
int rx, ry;
do {
rx = rand() % 10 + 1;
ry = rand() % 10 + 1;
} while(real_mine[rx][ry] == '1');
real_mine[rx][ry] = '1';
}
else
{
char val = count_mine(x, y);
show_mine[x][y] = val + '0';
open_mine(x, y);
}
break;
}
printf("Invalid input. Try again:\n");
}
}
int sweep_mine()
{
int x, y;
printf("Input coordinate: ");
scanf_s("%d%d", &x, &y);
if (x <1 || x >10 || y <1 || y >10)
{
printf("Out of bounds!\n");
return 0;
}
if (real_mine[x][y] == '1')
{
return 1; // 踩雷
}
else
{
char val = count_mine(x, y);
show_mine[x][y] = val + '0';
open_mine(x, y);
return 0;
}
}
void open_mine(int x, int y)
{
// 此处为简化版的相邻展开,非递归泛洪
// 仅处理紧邻的8个格子
struct offset {
int dx, dy;
} offsets[] = {{-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1}};
for(int i=0; i<8; i++) {
int nx = x + offsets[i].dx;
int ny = y + offsets[i].dy;
if (nx >=1 && nx <=10 && ny >=1 && ny <=10) {
if (show_mine[nx][ny] == '*' && real_mine[nx][ny] == '0') {
show_mine[nx][ny] = count_mine(nx, ny) + '0';
}
}
}
}
int count_show_mine()
{
int count = 0;
for (int i = 1; i <= row - 2; i++)
for (int j = 1; j <= col - 2; j++)
if (show_mine[i][j] == '*')
count++;
return count;
}
注意事项与扩展
上述代码实现了扫雷的基本骨架。在实际应用中,可能存在以下改进空间:
1. 递归展开:当前open_mine仅为一次级联,若遇到空格应递归调用自身以实现大面积白块翻开。
2. 内存布局:使用更大的数组(如13x13)并在外围填充'0',可以省去大量的边界判断代码,使count_mine逻辑更加简洁统一。
3. 界面优化:使用Windows API或ncurses库可以实现彩色显示和鼠标支持,提升控制台体验。
以上就是C语言实现扫雷小程序的详细内容,更多关于C语言游戏开发入门的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









