从零开始实现扫雷游戏:C语言数组与函数实战 前言扫雷是一款风靡全球的经典益智游戏它看似简单却蕴含着数组、循环、随机数、函数封装等许多基础编程思想。作为一个初学者在学完 C 语言的基本语法后常常会困惑这些知识到底能用来做什么。本文就以扫雷游戏为载体从零开始带你一步步分析需求、设计数据结构并最终用 C 语言在控制台中完整实现这款游戏。通过这个实战项目你不仅能巩固数组和函数的知识还能体会到把一个想法变成可运行程序的完整过程。一. 扫雷游戏分析与设计扫雷是一款经典的益智游戏玩家需要根据数字提示推断出雷的位置并避开所有雷完成排雷。本文将从零开始使用 C 语言在控制台中实现一个 9×9 棋盘、10 个雷的经典扫雷游戏帮助读者巩固数组和函数的相关知识。1.1 游戏功能说明本游戏需要实现以下功能使用控制台实现经典的扫雷游戏。游戏可以通过菜单实现继续玩或者退出游戏。扫雷的棋盘是 9×9 的格子。默认随机布置 10 个雷。可以排查雷如果位置不是雷就显示周围有几个雷如果位置是雷就炸死游戏结束把除 10 个雷之外的所有非雷都找出来排雷成功游戏结束。1.2 文件结构设计之前学习了多文件的形式对函数的声明和定义这里我们实践一下设计三个文件test.c文件中写游戏的测试逻辑。(负责游戏菜单和流程的控制)game.c文件中写游戏中函数的实现等。(存放雷初始化棋盘打印雷和排查雷game.h文件中写游戏需要的数据类型和函数声明等。(头文件常量定义和函数的声明注test.c和game.c中的所需要的头文件都可以写在game.h中之后只需要在它们自己文件中写一个#includegame.h就可以了。二.游戏菜单的制作我们可以制作一个简易菜单它包含最基础的“游玩”和“退出游戏”的选项。输入“1”表示“play”“0”则代表“exit”。void menu() { printf(***********************\n); printf(***** 1.play *****\n); printf(***** 0.exit *****\n); printf(***********************\n); }根据游戏可以重复玩的特性至少要显示一次菜单并且考虑到有分支的选项可以采用do-while循环加switch语句的结构。int main() { int input 0; do { menu(); printf(请选择:); scanf(%d, input); switch (input) { case 1: game(); break; case 0: printf(退出游戏\n); break; default: printf(选择错误请重新选择\n); } } while (input); return 0; }由代码可知switch语句中分三种情况输入1进入game()也就是游戏功能的实现部分输入0则是退出游戏除此之外则会报错。三.game()函数的实现(游戏功能的实现1.棋盘的制作与定义1.1制作的基本逻辑首先对于一个9×9的扫雷棋盘我们选择用二维数组来存储其中的信息分为两种信息1.存放布置好的雷的信息mine数组2.存放排查出雷的信息 show数组根据扫雷游戏的基本逻辑排查到某个位置时若该位置不是雷则需要展示出该位置周边的8个位置雷的个数。因此当我们要去制作一个 9×9的棋盘时这个棋盘的每条边在排查时都会出现数组越界访问的情况。想要解决这样的情况我们可以将这个棋盘扩大一圈变为 11×11 棋盘但是棋盘的信息还是存在9×9的棋盘里只是扩大一圈作为“边界保护”。1.2棋盘的定义二维数组的定义首先要看是什么类型。对于mine数组来说我们用0代表无雷1代表有雷信息为字符类型char。对于show数组来说属于玩家玩游戏时的棋盘全部用*信息也为字符类型char。并且为了后面可以灵活地改变数组的大小我们可以采用宏定义使用到了关键字define用ROWS表示行COLS表示列。char mine[ROWS][COLS];//存放布置好的雷 char show[ROWS][COLS];//存放排查出的雷的信息并将它们在game.h中声明#define ROW 9//棋盘行数 #define COL 9//棋盘列数 #define ROWS ROW2//数组行数 #define COLS COL2//数组列数2.棋盘的初始化mine数组全部初始化为0show数组: 全部初始化为*使用函数InitBoard(),向其中传入数组数组行数数组列数和要初始化的符号InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *);之后用循环嵌套的方式对二维数组进行初始化void InitBoard(char board[ROWS][COLS], int rows, int cols, char set) { int i 0; for (int i 0; i rows; i) { for (int j 0; j cols; j) { board[i][j] set; } } }3.棋盘的打印使用函数DisplayBoard()传入数组棋盘行数棋盘列数DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL);为了方便观察可以打印出行号和列号void DisplayBoard(char board[ROWS][COLS], int row, int col) { printf(---------扫雷游戏--------\n); for (int i 0; i col; i) { printf(%d , i);//打印行号 } printf(\n); for (int i 1; i row; i) { printf(%d , i);//打印列号 for (int j 0; j col; j) { printf(%c , board[i][j]); } printf(\n); } }4.雷的布置使用函数SetMine()传入数组棋盘行数棋盘列数SetMine(mine, ROW, COL);4.1随机生成雷为了可以随机生成10个雷 可以在函数内部使用rand()函数来生成随机数,而rand()函数调用之前需要调用srand(),当传递给srand()的数值(类型为unsigend int)也叫做种子不断变化时rand()函数每次调用就会返回随机的值。因此可以使用time()函数来设置一个随时间变化的种子当time()函数的参数timer(类型为time_t)为NULL时就会返回当前日历的时间。因此time(NULL)就是一个随时间变化的种子所以需要在主函数里添加srand((unsigned int)time(NULL));int main() { int input 0; do { srand((unsigned int)time(NULL)); menu(); printf(请选择:gt;); scanf(%d, amp;input); switch (input) { case 1: game(); break; case 0: printf(退出游戏\n); break; default: printf(选择错误请重新选择\n); } } while (input); return 0; }这里需要的头文件为#includestdlib.h #includetime.h由于rand()的返回值范围较大但是我们的棋盘目前是9×9 的可以通过取模运算将它缩小到我们能用的有效范围int x rand() % row 1;//余数原本为0~8加一后变为0~9 int y rand() % col 1;//同上4.2布置十个雷定义一个EASY_COUNT用来统计雷的个数#define EASY_COUNT 10然后通过while循环在棋盘上随机布置10个雷oid SetMine(char board[ROWS][COLS], int row, int col) { //布置十个雷 //生成随机的坐标布置雷 int count EASY_COUNT; while (count) { int x rand() % row 1; int y rand() % col 1; if (board[x][y] 0) { board[x][y]1; count--; } } }5.雷的查找5.1排雷的基本逻辑使用函数FindMine(),传入mine数组show数组棋盘行数棋盘列数由于前面在棋盘上打印了行号和列号因此我们可以通过输入坐标来进行排查。扫雷游戏获胜的条件是要把所有不是雷的位置全部找出来因此我们可以用while循环去遍历每一个不是雷的位置while (win row * col - EASY_COUNT)之后判断输入的坐标是否合法再将坐标带入到mine数组中去进行比对。并且无论是否是雷排一次雷最后都要打印一次show数组的棋盘以便于下一次排雷的进行。5.2排雷的情况排雷的结果就两种情况要么是雷“被炸死了”要么不是雷之后进入到下一次循环直到游戏胜利当排查的位置不是雷时就要在这个坐标上输出相邻周围8个位置上雷的个数。因此我们可以创建一个新的函数GetMine()来计算这个信息。使用GetMine()传入mine数组和排查时输入的坐标。int GetMineCount(char mine[ROWS][COLS], 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);//字符ASCII码值相减后为数字1-049-481 }此函数功能的分析由于mine数组中为1和0二者都是字符二者的ASCII值分别为49和48那么当二者相减时就会得到数字1那么此时这个数字1就可以表示为雷的数量。也就是说当排查坐标周围八个位置上的字符加起来并减去8*0,返回的结果即为雷的数量。因为最后打印show数组的棋盘上显示的应该是字符那么得到的返回值再0就可以得到相应数字的字符从而显示出周围雷的数量。5.3排雷总结最终代码如下void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x 0; int y 0; int win 0; while (win row * col - EASY_COUNT) { printf(请输入要排查的坐标:); scanf(%d %d, x, y); if (x 1 x row y 1 y col) //判断坐标是否合法 { if (mine[x][y] 1) { printf(很遗憾你被炸死了\n); DisplayBoard(show, ROW, COL); break; } else { //该位置不是雷就统计这个坐标周围有几个雷 int count GetMineCount(mine, x, y); show[x][y] count 0;//要显示出字符就要给相应的ASCII码值 DisplayBoard(show, ROW, COL); win; } } else { printf(坐标非法重新输入\n); } } if (win row * col - EASY_COUNT) { printf(排雷成功\n); DisplayBoard(mine, ROW, COL); } }6.game()函数最终结果void game() { char mine[ROWS][COLS];//存放布置好的雷 char show[ROWS][COLS];//存放排查出的雷的信息 //接下来要初始化棋盘 //1.mine数组最开始全是0 //2.show数组最开始全是’*‘ InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //之后要打印棋盘 DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); //之后要布置雷 SetMine(mine, ROW, COL); //最后排查雷 FindMine(mine, show, ROW, COL); }四.最终的game.h#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #includestdio.h #includestdlib.h #includetime.h #define EASY_COUNT 10 #define ROW 9 #define COL 9 #define ROWS ROW2 #define COLS COL2 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//初始化棋盘的函数声明 void DisplayBoard(char board[ROWS][COLS],int row, int col);//打印棋盘 void SetMine(char board[ROWS][COLS], int row, int col);//布置雷 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排查雷 int GetMine(char mine[ROWS][COLS], int x, int y);//查照该坐标周围八个位置中有多少雷五.扫雷总结到此为止一个基础版的扫雷游戏就做好了在其中我们用到了通过函数对游戏功能进行拆分二维数组来存储棋盘数据宏定义棋盘大小循环嵌套对棋盘进行初始化rand()生成随机数取模运算缩小取值范围字符与数字之间的转换作为一个C语言的初学者第一次完整地记录自己做项目的过程我觉得这能帮助我加深对知识的理解希望也能帮到有需要的人如有错误欢迎各位大佬前来指正