news 2026/4/26 14:05:39

扫雷游戏(C 语言)完整版

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
扫雷游戏(C 语言)完整版

一、项目文件结构

  1. game.h—— 头文件、宏定义、函数声明
  2. game.c—— 游戏函数实现(你的原版代码 + 注释)
  3. test.c—— 主函数入口

二、完整代码(100% 保留你的注释)

1. game.h

//扫雷游戏用到的头文件和函数声明 #pragma once #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #include<stdio.h> #include<stdlib.h> #include<time.h> void menu(); void test(); //扫雷游戏的主逻辑 void game();//扫雷游戏的过程 //初始化棋盘 void InitBoard(char board[ ROWS][COLS], int rows, int cols, char ch); //打印棋盘 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); //递归函数实现展开一片的效果 void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* win);

2. game.c

//扫雷游戏的主体函数 #define _CRT_SECURE_NO_WARNINGS 1 #include"game.h"//自己写的头文件 → 用 " " 双引号 void test() //扫雷游戏的入口 { int input = 0; do { menu();//打印游戏的菜单 printf("请选择接下来的操作:\n"); scanf("%d", &input); switch (input) { case 1: printf("开始游戏!\n"); game(); break; case 0: printf("退出游戏!\n"); break; default: printf("选择错误,重新选择!!!\n"); break; } } while (input); } void menu()//扫雷游戏的菜单 { printf("***********************************************\n"); printf("*********************扫雷游戏******************\n"); printf("*************** 1.0 play **************\n"); printf("*************** 0.0 exit **************\n"); printf("*********************扫雷游戏******************\n"); printf("***********************************************\n"); } void game()//扫雷游戏的过程 { char mine[ROWS][COLS];//存放布置好的雷的信息 本游戏默认 0不是雷,1是雷 char show[ROWS][COLS];//存放排查出雷的信息 //初始化棋盘函数的调用 InitBoard(mine, ROWS, COLS, '0');//'0' InitBoard(show, ROWS, COLS, '*');//'*' //布置雷函数的调用 SetMine(mine, ROW, COL); //打印棋盘函数的调用 DisplayBoard(show, ROW, COL); //排查雷函数的调用 FindMine(mine, show, ROW, COL); } //初始化棋盘函数 void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch)//char ch 的意思就是不把初始化写死了,初始化成什么由传的参数决定 { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { board[i][j] = ch;//ch这个字符会因为 函数调用时会因参数不同而不同 } } } //打印棋盘函数 void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘 { for (int i = 0; i <= row; i++)//打印列号 { printf("%d ", i); } printf("\n"); for (int i = 1; i <= row; i++) { printf("%d ", i);//打印行号 for (int j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } } //布置雷函数 void SetMine(char board[ROWS][COLS], int row, int col) { //布置10个雷 随机生成坐标布置 // 想要随机位置,那只有一个办法就是C语言随机数 srand()函数 srand((unsigned int)time(NULL)); //还要考虑坐标的范围 int count = 10;//一共要布置的雷的个数 while (count) { int x = rand() % row + 1;//1-9 int y = rand() % col + 1;//1-9 if (board[x][y] == '0')//判断这个坐标是否已经布置雷了 { board[x][y] = '1';//没有布置,那就布置雷 count--;//成功布置雷的次数才减1 } } } //统计玩家所选的坐标周围有几个雷 int get_mine_count(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'; //-8*'0'是把字符数字转整型数字 } // 递归展开空白区域 void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* win) { // 1. 越界了 → 退出 if (x < 1 || x > row || y < 1 || y > col) return; // 2. 已经翻开了 → 退出 if (show[x][y] != '*') return; // 3. 统计周围雷数 int count = get_mine_count(mine, x, y); // 4. 周围有雷 → 只显示数字,不展开 if (count > 0) { show[x][y] = count + '0'; (*win)++; return; } // ====================== // 5. 周围没雷 → 显示空格! // ====================== else { show[x][y] = ' '; (*win)++; // 6. 递归展开8个方向 ExpandMine(mine, show, row, col, x - 1, y, win); ExpandMine(mine, show, row, col, x + 1, y, win); ExpandMine(mine, show, row, col, x, y - 1, win); ExpandMine(mine, show, row, col, x, y + 1, win); ExpandMine(mine, show, row, col, x - 1, y - 1, win); ExpandMine(mine, show, row, col, x - 1, y + 1, win); ExpandMine(mine, show, row, col, x + 1, y - 1, win); ExpandMine(mine, show, row, col, x + 1, y + 1, win); } } //排查雷函数 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 - 10) { printf("请输入你要排查的坐标:\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了!\n"); DisplayBoard(mine, ROW, COL);//打印整个棋盘,让玩家知道炸死自己的雷在哪 break; } else { //判断这个坐标是否被排查过 if (show[x][y] == '*') { ExpandMine(mine, show, row, col, x, y, &win); DisplayBoard(show, ROW, COL); } else { printf("该坐标被排查过,不能重新排查!\n"); } } } else { printf("坐标非法,请重新输入\n"); } } if (win == row * col - 10) { printf("恭喜你,排雷成功!\n"); DisplayBoard(mine, ROW, COL); } }

3. test.c

//测试游戏的逻辑 #define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //自己写的头文件 → 用 " " 双引号 int main() { test(); //扫雷游戏的主逻辑 return 0; }

三、编写过程遇到的问题与个人思考(第一人称叙述)

问题 1:数组初始化与InitBoard初始化是否重复多余?

最开始我在定义二维数组时,习惯性写成char mine[ROWS][COLS] = {'0'};后面又调用InitBoard函数统一初始化棋盘,我疑惑这样是不是多余操作。

后来我理解到:C 语言中部分初始化只会给数组第一个元素赋值,其余元素默认初始化为\0,并不能把整个棋盘全部初始化为指定字符。真正能完整遍历二维数组、统一赋值的是InitBoard初始化函数。所以正确写法是:只定义数组,不手动初始化,全权交给初始化函数处理,避免棋盘数据错乱。

问题 2:递归函数中return;的作用

在递归展开函数中,我写了两处判断后直接return;,一开始不清楚这行代码的意义。

经过理解我明白:return;在无返回值函数中,代表直接结束当前函数,后续代码不会执行。

  1. 坐标越界时return:防止递归访问棋盘外的非法空间,避免程序崩溃;
  2. 格子已经翻开时return:防止重复递归、无效调用,优化代码逻辑。return;是递归的终止条件,也是整个递归逻辑的安全出口,必不可少。

问题 3:八方向递归调用,是否必须写在else内部?

我思考过:递归展开八方向的代码,能不能写在else外面。

最后理清逻辑:if(count > 0)代表当前格子周围存在雷,只需要显示数字,不需要继续向外展开;else代表周围没有雷,才需要显示空白格子,同时递归遍历八个相邻位置,实现大片展开。如果递归写在外面,有雷的格子也会触发递归,造成逻辑混乱,所以递归代码必须放在 else 大括号内部

问题 4:统计胜利的变量win为什么要用指针,传参为什么是&win

这是我最大的疑惑:为什么不能直接传普通变量win

我的理解:

  1. 普通变量传参是值传递,函数内部修改的只是临时副本,无法改变外部真实的win变量;
  2. 我需要在递归函数中,实时修改外部排雷计数win,用来判断游戏胜负;
  3. 想要跨函数修改变量,必须传递变量地址,用指针接收;
  4. FindMinewin是普通整型变量,所以传参要写&win取地址;
  5. ExpandMine形参本身就是指针,内部递归调用时,直接传递指针变量win即可,不用再加取地址符。

问题 5:排查雷函数已经做了坐标合法判断,递归还需要重复判断吗?

FindMine中已经限制了玩家输入的坐标范围,我一开始觉得递归里的越界判断是多余的。

实际思考后发现两者完全不一样:

  1. FindMine判断的是玩家手动输入的坐标;
  2. 递归是程序自动向上下左右八个方向遍历,会自动生成超出棋盘范围的坐标;玩家输入不会越界,但递归一定会访问边界外的位置,所以递归内部的越界判断不能删除,二者各司其职,缺一不可。

问题 6:递归函数的调用逻辑

我梳理了整体调用流程:

  1. 程序运行,玩家选择开始游戏,进入game函数;
  2. 完成棋盘初始化、布置地雷、打印初始棋盘;
  3. 玩家输入排查坐标,进入FindMine函数;
  4. 坐标合法且未踩雷、未被翻开时,调用ExpandMine递归函数;
  5. 空白格子自动触发八方向递归,遇到数字、边界、已翻开格子自动终止;
  6. 不断统计翻开格子数量,全部非雷格子翻开后,判定游戏胜利。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/26 14:01:50

3分钟极速搞定OFD转PDF:免费开源神器Ofd2Pdf完整指南

3分钟极速搞定OFD转PDF&#xff1a;免费开源神器Ofd2Pdf完整指南 【免费下载链接】Ofd2Pdf Convert OFD files to PDF files. 项目地址: https://gitcode.com/gh_mirrors/ofd/Ofd2Pdf 还在为电子发票、政府公文等OFD格式文件无法在手机、电脑上正常打开而烦恼吗&#xf…

作者头像 李华