C语言旅程之扫雷游戏(基础版讲解)
一、扫雷游戏介绍《扫雷》是一款经典的逻辑益智单机游戏1992 年随 Windows 3.1 系统推出后风靡全球最初设计目的是帮助用户熟悉鼠标操作如今仍是不少人休闲与锻炼逻辑思维的选择以下是其详细介绍1. 界面元素包含雷区、左上角的剩余地雷计数器和右上角的计时装置。雷区方格有未打开、已打开、插旗标记三种状态已打开方格若显示数字就代表其 3×3 范围内的地雷数量显示空白则意味着周围无地雷。2. 分级难度常规分三个固定难度具体如下初级9×9 方格配 10 颗地雷中级16×16 方格配 40 颗地雷高级16×30 方格配 99 颗地雷此外还有自定义模式可自主设定雷区大小和地雷数量不过多数版本中雷区最大不超过 24×30。3. 基础玩法3.1 核心目标在不触发地雷的前提下尽快揭开所有安全方格若误点地雷则游戏直接结束。3.2 操作方式左键单击用于揭开方格若揭开的是空白方格会连带打开相邻的安全方格右键单击可给疑似地雷的方格插旗标记再次点击能取消标记左右键双击当数字周围标记的地雷数与数字一致时双击该数字可自动揭开其周围未打开的方格二、代码实现过程用到的知识一函数的运用提升可读性拆分功能为函数如扫雷的雷区初始化、计时逻辑清晰凭函数名可懂用途降低理解成本。减少冗余重复功能如扫雷插旗逻辑只需写一次函数多次调用避免重复编码改代码时仅改对应函数即可。易维护调试问题可定位到具体函数如雷区出错查初始化函数不用全局找错新增功能如扫雷难度保存只需加新函数不影响旧代码。方便协作多人可分工写不同函数一人做界面、一人处理操作各模块独立测试减少冲突提高效率。契合结构化设计按 “自顶向下” 拆分复杂程序如扫雷拆分为初始化 - 操作 - 判断结束避免代码混乱。二C 语言中函数、主程序、头文件各自独立成文件函数独立成文件实现功能模块化如扫雷中将 “雷区初始化”“计算地雷数” 等函数分存单个文件聚焦单一功能便于定位修改逻辑支持多人分工编写减少代码冲突且函数文件可在多项目中复用提升开发效率。主程序main 函数独立成文件明确区分程序 “启动逻辑” 与 “功能实现”如修改扫雷的启动流程或难度选择顺序时无需改动其他功能文件降低对整体代码的影响也能让开发者快速识别程序核心执行脉络。头文件独立成文件可集中声明函数、宏定义等如将扫雷各功能函数声明统一存放其他源文件通过 #include 引入即可调用避免重复声明保证接口一致性同时隐藏函数实现细节仅暴露调用接口简化调用逻辑方便统一维护接口规范。三、扫雷游戏 C 语言实现过程一游戏选择界面实现首先制作游戏选择界面判断用户是否要玩游戏可用 do-while 以及 switch 函数实现1 代表要玩0 代表退出以下是程序的入口int main() { int input 0; do { Interface(); printf(请输入数字1或0\n); scanf(%d, input); switch (input) { case 1: game(); break; case 0: printf(结束游戏\n); break; default: printf(输入错误请重新输入\n); } } while (input); return 0; }其中选择界面函数如下void Interface() //游戏开始界面 { printf(* * * * * * * * * * *\n); printf(* * * * * * * * * * *\n); printf(* * * 1. play * * *\n); printf(* * * 0. exit * * *\n); printf(* * * * * * * * * * *\n); printf(* * * * * * * * * * *\n); }二棋盘设计与相关问题解决以基础版 9×9 棋盘为例先思考以下问题并解决思考问题如何用二维数组制作棋盘并存放数据用 1 代表雷0 代表不是雷不仅要生成存放数据的界面还要展示给玩家该如何处理是否要用整型 1 和 0 来代表是否是雷有没有更好的解决办法9×9 的棋盘对于边界数据最外层的雷怎么判断问题解答可生成两个棋盘一个为游戏界面向玩家展示用 * 表示棋盘采用 char 类型的二维数组另一个为数据棋盘存放雷的位置信息。向玩家展现的棋盘用 char 类型数据棋盘也用 char 类型避免单独为整型数据写一个数组简化代码。对于边界数据判断问题可将棋盘扩大到191×191即 11×11 的棋盘初始化棋盘时边界也初始化为‘0’统计时可正常统计不影响雷的数量且化繁为简向玩家显示棋盘时选择打印 9×9 的棋盘即可。相关代码实现宏定义棋盘规格//棋盘规格 #define horizontal 9 #define vertical 9 //存放规格扩大后的棋盘 #define horizontals vertical 2 #define verticals vertical 2定义棋盘数组char content[verticals][horizontals];//存放雷的数据 char show[verticals][horizontals];//生成界面初始化棋盘//初始化棋盘埋雷 //content开始全是“0” //show开始全是“*” Initialization(content, verticals, horizontals, 0); //数据棋盘传入‘0’ Initialization(show, verticals, horizontals, *); //显示棋盘传入‘*’显示棋盘//棋盘显示 //Desplaygame(content, vertical, vertical);//数据前期打印出数据方便调试 Desplaygame(show, vertical, horizontal);//游戏界面布置雷与排查雷//布置雷 Set(content, vertical, horizontal); //排查雷 Find(content, show, verticals, horizontals);三初始化棋盘函数void Initialization(char Ip[verticals][horizontals], int c, int v, char set) //设置棋盘 { int i 0; for (i 0; i verticals; i) { int j 0; for (j 0; j horizontals; j) { Ip[i][j] set; } } }这时我们发现坐标输入时要自己数很麻烦那么怎么优化一下显而易见需要我们加上坐标轴.四棋盘显示函数含坐标轴优化最初的棋盘显示函数未加坐标轴用户输入坐标时需自行计数较为麻烦优化后添加坐标轴的完整代码如下void Desplaygame(char Ip[verticals][horizontals], int row, int col) //棋盘显示 { int i 0; printf(------扫雷游戏------\n); for (i 0; i col; i) //打印横坐标 { printf(%d , i); } printf(\n); for (i 1; i row; i) { int j 0; printf(%d, i); //打印纵坐标 printf( ); for (j 1; j col; j) { printf(%c , Ip[i][j]); } printf(\n); } }五布置雷函数定义雷的个数当然后期可以增加数量以及让用户自定义难度使用while循环语句直到雷的个数减为零停止循环需要生成随机坐标借助srand函数#includestdlib.h和time函数#includetime.h下文对这两个函数介绍const int EASY_COUNT 10; //埋雷个数布置雷代码void Set(char Ip[verticals][horizontals], int row, int col) //布雷 { //布置十个雷 int count EASY_COUNT; while (count) { int x rand() % row 1; //1-9随机数后文加解释 int y rand() % col 1; if (Ip[x][y] 0) { Ip[x][y] 1; count--; } } }随机数生成初始化在 main 函数内加上以下代码确保每次运行程序生成不同的随机数序列srand((unsigned int)time(NULL));调用 time (NULL) 获取当前系统的秒级时间戳将其强制转换为 unsigned int 类型后作为 “随机数种子” 传入 srand 函数初始化随机数生成器确保后续调用 rand () 时每次程序运行都能得到不同的随机数序列。六排查雷函数6.1 思考问题如果踩到雷游戏直接结束是否不妥如何计算周围雷的个数怎么判断玩家成功排雷6.2 问题解答踩到雷后应打印出雷的数据棋盘让玩家输得明白。计算周围雷的个数有两种方法一是把周围八个格子是‘1’的坐标相加二是用循环从 x-1 到 x1y-1 到 y1统计‘1’的个数第二种方法中多加的中间格子无影响因为若中间格子是‘1’游戏早已结束。判断玩家成功排雷可计算玩家排查出的无雷数量当排查无雷数量等于棋盘总坐标数减去雷的数量时游戏成功完成后续可优化游戏改变判断条件。6.3 排查雷代码void Find(char Ip[verticals][horizontals], char show[verticals][horizontals], int row, int col) { int x 0; int y 0; int win 0; while (win row * col - EASY_COUNT) { printf(请输入要排查的坐标例如1 3中间空格先排后列 \n); scanf(%d %d, x, y); if (x 1 y 1 x row y col) { if (Ip[x][y] 1) { printf(对不起你踩雷了\n); { Desplaygame(Ip, vertical, horizontal); break; } } else { //计算周围雷的个数 int count GetIpCount(Ip, x, y); show[x][y] count 0; Desplaygame(show, vertical, horizontal); //Desplaygame(Ip, vertical, horizontal);//---- win; } } else { printf(输入不合法重新输入 \n); } } if (win row * col - EASY_COUNT) { printf(恭喜你排雷成功\n); Desplaygame(Ip, vertical, horizontal); } }七统计雷的个数函数7.1 方法一int GetIpCount(char mine[verticals][horizontals], int x, int y) { return (mine[x - 1][y] mine[x - 1][y - 1] mine[x][y - 1] mine[x 1][y - 1] mine[x 1][y] mine[x 1][y 1] mine[x][y 1] mine[x - 1][y 1] - 8 * 0); }7.2 方法二int GetIpCount(char mine[verticals][horizontals], int x, int y)//统计雷的数目 { char a 0; for (int i -1; i 1; i) { for (int j -1; j 1; j) { if (mine[x i][y j] 1) { a 1; } } } return a; }四、完整代码一game.h头文件#pragma once #define _CRT_SECURE_NO_WARNINGS #includetime.h #includestdio.h #includestdlib.h //棋盘规格 #define horizontal 9 #define vertical 9 //存放规格 #define horizontals vertical 2 #define verticals vertical 2 //埋雷个数 const int EASY_COUNT 10; void Initialization(char Ip[verticals][horizontals], int c, int v, char set);//生成界面 void Desplaygame(char Ip[verticals][horizontals], int row, int col);//打印棋盘 void Set(char Ip[verticals][horizontals], int row, int col);//布置雷 void Find(char Ip[horizontals][verticals], char show[horizontals][verticals], int row, int col);//排查雷二game.c函数实现文件#includegame.h //统计雷的数目 int GetIpCount(char mine[verticals][horizontals], int x, int y) { char a 0; for (int i -1; i 1; i) { for (int j -1; j 1; j) { if (mine[x i][y j] 1) { a 1; } } } return a; } //设置棋盘 void Initialization(char Ip[verticals][horizontals], int c, int v, char set) { int i 0; for (i 0; i verticals; i) { int j 0; for (j 0; j horizontals; j) { Ip[i][j] set; } } } //棋盘显示 void Desplaygame(char Ip[verticals][horizontals], int row, int col) { int i 0; printf(------扫雷游戏------\n); for (i 0; i col; i) { printf(%d , i); } printf(\n); for (i 1; i row; i) { int j 0; printf(%d, i); printf( ); for (j 1; j col; j) { printf(%c , Ip[i][j]); } printf(\n); } } //布雷 void Set(char Ip[verticals][horizontals], int row, int col) { //布置十个雷 int count EASY_COUNT; while (count) { int x rand() % row 1; int y rand() % col 1; if (Ip[x][y] 0) { Ip[x][y] 1; count--; } } } //排查雷 void Find(char Ip[verticals][horizontals], char show[verticals][horizontals], int row, int col) { int x 0; int y 0; int win 0; while (win row * col - EASY_COUNT) { printf(请输入要排查的坐标例如1 3中间空格先排后列 \n); scanf(%d %d, x, y); if (x 1 y 1 x row y col) { if (Ip[x][y] 1) { printf(对不起你踩雷了\n); { Desplaygame(Ip, vertical, horizontal); break; } } else { //计算周围雷的个数 int count GetIpCount(Ip, x, y); show[x][y] count 0; Desplaygame(show, vertical, horizontal); //Desplaygame(Ip, vertical, horizontal);//---- win; } } else { printf(输入不合法重新输入 \n); } } if (win row * col - EASY_COUNT) { printf(恭喜你排雷成功\n); Desplaygame(Ip, vertical, horizontal); } }三test.c主程序文件#includegame.h //游戏开始界面 void Interface() { printf(* * * * * * * * * * *\n); printf(* * * * * * * * * * *\n); printf(* * * 1. play * * *\n); printf(* * * 0. exit * * *\n); printf(* * * * * * * * * * *\n); printf(* * * * * * * * * * *\n); } //游戏主题 void game() { char content[verticals][horizontals];//存放雷的数据 char show[verticals][horizontals];//生成界面 //初始化棋盘埋雷 //content开始全是“0” //show开始全是“*” Initialization(content, verticals, horizontals, 0); Initialization(show, verticals, horizontals, *); //棋盘显示 //Desplaygame(content, vertical, vertical);//数据 Desplaygame(show, vertical, horizontal);//游戏界面 //布置雷 Set(content, vertical, horizontal); //排查雷 Find(content, show, verticals, horizontals); } int main() { int input 0; srand((unsigned int)time(NULL)); do { Interface(); printf(请输入数字1或0\n); scanf(%d, input); switch (input) { case 1: game(); break; case 0: printf(结束游戏\n); break; default: printf(输入错误请重新输入\n); } } while (input); return 0; }五、补充知识一srand 函数与 time 函数C 语言介绍1. 核心作用两者配合使用的核心目的生成真正的随机数避免每次运行程序得到相同的随机序列尤其适合扫雷等需要随机布置地雷的场景。2. 各自介绍1srand 函数#includestdlib.h功能设置随机数生成器的 “种子”随机数序列的起始值。特性若种子固定如 srand (10)每次运行程序rand () 生成的随机数序列完全相同伪随机无返回值参数为 unsigned int 类型种子值需搭配 rand () 函数使用且程序中通常只调用 1 次多次调用会导致随机序列混乱。示例srand (123);固定种子随机序列固定2time 函数#includetime.h功能获取当前系统时间从 1970 年 1 月 1 日 00:00:00 到当前的秒数返回值为 time_t 类型本质是整数。特性参数通常传 NULL表示直接返回当前时间核心价值时间时刻变化返回值永远不同可作为 srand 的种子实现 “每次运行种子不同”。示例time (NULL);返回当前系统时间的秒数3. 扫雷场景核心用法随机布置地雷通过 time (NULL) 获取动态种子结合 srand 初始化随机数生成器再用 rand () 生成随机坐标布置地雷#includestdlib.h #includetime.h int main() { // 初始化随机数种子程序开头调用1次即可 srand((unsigned int)time(NULL)); // 强制转换time_t为unsigned int // 生成0~8的随机行、列坐标适配9×9雷区 int row rand() % 9; // rand()%n 生成0~n-1的随机数 int col rand() % 9; // 用生成的坐标布置地雷如mine[row][col] # return 0; }4. 关键注意事项srand 只需调用 1 次通常放在 main 函数开头多次调用会导致随机数重复必须包含对应头文件stdlib.hsrand/rand和 time.htime若不调用 srandrand () 默认使用种子 1每次运行随机序列完全相同。二随机坐标生成代码解析int x rand () % row 1; int y rand () % col 1;1. rand () 函数功能生成 0~RAND_MAX通常是 32767的伪随机整数需先通过 srand ((unsigned int) time (NULL)) 初始化种子否则每次运行序列相同头文件依赖#include stdlib.h。2. % row取模运算作用限制随机数的上限结果范围是 0~(row-1)示例若 row9则 rand ()%9 会得到 0、1、2、…、8 中的随机数取模后余数不会超过除数。3. 1的作用作用将随机数的范围整体上移 1把 0~(row-1) 转为 1~row示例rand ()%9 1 最终得到 1、2、…、9即注释中 “1-9 随机数” 的实现。4. 扫雷场景的实际意义若扫雷雷区是 9×9行、列数均为 9则row9 时x 生成 1~9 的随机行号col9 时y 生成 1~9 的随机列号结合这两个坐标可实现 “随机在雷区布置地雷”如 mine [x][y] #。5. 关键注意需确保 row 和 col 是正整数否则取模可能出错若雷区坐标习惯从 0 开始如 0~8可去掉 1直接写 rand ()% row必须在调用 rand () 前初始化种子srand ((unsigned int) time (NULL))否则每次运行会生成相同的坐标地雷位置固定。六、总结1.继续扩展这个游戏1.可以增加游戏难度让玩家有选择简单9*9棋盘10个雷中等16*16棋盘40个雷困难30*16棋盘99个雷2.如果排查位置及周围没有雷直接展开周围一片提高游戏体验3.增加游戏功能可以标记雷4.可以增加排雷时间显示。2.作者寄语亲爱的读者衷心感谢你抽出宝贵时间阅读这份扫雷游戏的介绍与 C 语言实现指南无论是出于学习 C 语言的需求、对经典游戏的热爱还是对编程实践的探索你的关注都是这份内容最珍贵的意义。在编程学习的道路上每一次尝试、每一次调试都离不开耐心与坚持。希望这份教程能为你提供清晰的思路、实用的技巧也能让你在实践中感受到编程的乐趣与成就感。愿你在后续的学习中既能攻克技术难关也能收获满满的知识与成长愿每一段代码都能顺遂运行每一个创意都能落地生根。再次感谢你的陪伴与支持祝你学习进步、万事顺意在编程的世界里不断探索新可能收获属于自己的精彩