news 2026/8/6 6:01:14

C语言学习笔记(四):扫雷

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言学习笔记(四):扫雷

目录

  • 一、游戏功能预览
  • 二、设计思路与分析
    • 2.1 数据存储方案
    • 2.2 为什么要多开一圈
    • 2.3 两个数组各司其职
  • 三、文件结构
  • 四、代码实现(逐块讲解)
    • 4.1 头文件 game1.h
    • 4.2 主程序 test1.c
    • 4.3 游戏逻辑 game1.c
  • 五、总结

一、游戏功能预览

我们实现的是一个9×9的扫雷棋盘,默认埋有10颗雷。玩家通过输入坐标(行 列)来翻开格子:

  • 如果踩到雷 → 游戏结束(被炸死)
  • 如果没踩到雷 → 显示该位置周围 8 个格子中雷的总数
  • 如果把所有非雷格子全部翻开 → 排雷成功,游戏胜利

所有操作都在黑框(控制台)中进行,界面清晰,适合初学者理解数组和函数的综合运用。


二、设计思路与分析

2.1 数据存储方案

我们需要存储两类信息:

  • 雷的分布(哪些格子有雷)
  • 玩家已翻开格子的提示数字(周围雷数)

最直观的想法是用一个9×9的二维数组,用1表示雷,0表示非雷。但这样做会带来两个问题:

  1. 当统计边缘格子周围雷数时,可能会访问越界(比如第 9 行第 9 列,它的下方和右方都没有格子)。
  2. 如果把雷数(数字)和雷标记(1/0)混在一起,显示时容易混淆。

2.2 为什么要多开一圈

为了避免越界,我们故意把数组扩大成11×11,但只在中间的9×9(第 1~9 行,第 1~9 列)布置雷和进行游戏。外围的一圈(第 0 行、第 10 行、第 0 列、第 10 列)全部初始化为'0'(无雷),这样在统计边缘格子时,即使访问到外围,也不会越界,而且外围没有雷,不影响计数。

2.3 两个数组各司其职

我们使用两个独立的数组

  • mine数组:存放雷的分布('0'无雷,'1'有雷)——对玩家隐藏
  • show数组:存放玩家看到的界面(初始全部为'*',翻开后显示数字)——展示给玩家

这样职责清晰,互不干扰。


三、文件结构

按照模块化编程的习惯,我们将代码拆分为三个文件:

文件作用
game1.h头文件,包含宏定义、函数声明
game1.c游戏函数的实现(初始化、打印、布置雷、排查雷)
test1.c主程序,包含菜单和游戏流程控制

四、代码实现(逐块讲解)

4.1 头文件 `game1.h`

#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<time.h>#defineROW9// 实际棋盘行数#defineCOL9// 实际棋盘列数#defineROWSROW+2// 数组行数(多一圈)#defineCOLSCOL+2// 数组列数(多一圈)#defineEASY_COUNT10// 雷的数量// 函数声明voidInitBoard(charboard[ROWS][COLS],intr,intc,charset);voidDisplayBoard(charboard[ROWS][COLS],intr,intc);voidSetMine(charmine[ROWS][COLS],intr,intc);voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc);

解释

  • #define定义常量,方便日后修改棋盘大小或雷数。
  • 函数声明让其他.c文件可以调用这些函数。

4.2 主程序 `test1.c`

#define_CRT_SECURE_NO_WARNINGS#include"game1.h"voidmenu(){printf("------------------\n");printf("------1.play------\n");printf("------0.exit------\n");printf("------------------\n");}voidgame(){charmine[ROWS][COLS]={0};// 存放雷的数组charshow[ROWS][COLS]={0};// 展示给玩家的数组// 1. 初始化InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');// 2. 打印初始棋盘(只显示 show)// DisplayBoard(mine, ROW, COL); // (调试时可查看雷的位置)DisplayBoard(show,ROW,COL);// 3. 布置雷SetMine(mine,ROW,COL);// 4. 排查雷FindMine(mine,show,ROW,COL);}intmain(){intinput=0;srand((unsignedint)time(NULL));// 随机种子,用于生成雷的位置do{menu();printf("请输入:");scanf("%d",&input);switch(input){case1:game();break;case0:printf("退出游戏\n");break;default:printf("输入错误,请重新输入\n");break;}}while(input);return0;}

流程

  1. 显示菜单,用户选择1开始游戏,0退出。
  2. game()函数中:
    • 定义两个二维数组。
    • 初始化mine'0'show'*'
    • 打印show棋盘(玩家看到的是星号)。
    • 随机布置雷(只布置在mine中)。
    • 进入排雷循环。

4.3 游戏逻辑 `game1.c`

初始化棋盘
voidInitBoard(charboard[ROWS][COLS],intr,intc,charset){for(inti=0;i<r;i++){for(intj=0;j<c;j++){board[i][j]=set;}}}

作用:将整个数组(包括外围一圈)全部设置为同一个字符('0''*')。


打印棋盘
voidDisplayBoard(charboard[ROWS][COLS],intr,intc){// 打印列号(0~9)for(inti=0;i<=c;i++){printf("%d ",i);}printf("\n");// 打印行号 + 棋盘内容(只打印 1~r 行,1~c 列)for(inti=1;i<=r;i++){printf("%d ",i);for(intj=1;j<=c;j++){printf("%c ",board[i][j]);}printf("\n");}printf("\n");}

注意:我们只打印中间的rc列(即1~ROW1~COL),外围一圈不打印。


布置雷
voidSetMine(charmine[ROWS][COLS],intr,intc){intcount=EASY_COUNT;while(count){intx=rand()%r+1;// 随机行 1~rinty=rand()%c+1;// 随机列 1~cif(mine[x][y]!='1')// 该位置还没有雷{mine[x][y]='1';count--;}}}

解释

  • rand() % r + 1生成1~r的随机数。
  • 避免重复布置(如果已经是'1'就重新生成)。

统计周围雷数

这是一个静态函数(只在game1.c内部使用),用于计算某个格子周围 8 个位置的雷数:

staticsize_tGetMineCount(charmine[ROWS][COLS],intx,inty){returnmine[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'
因为mine中存储的是字符'0''1',它们的 ASCII 码值分别是 48 和 49。八个字符相加后,需要减去 8 个'0'(即8*48)才能得到真正的数字雷数。返回值类型为size_t(无符号整数)。


排查雷(核心)
voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc){intx=0;inty=0;intwin=0;// 已排查(非雷)的格子数while(win<r*c-EASY_COUNT)// 当未翻开的非雷格子数 > 0{printf("请输入坐标:");scanf("%d%d",&x,&y);// 1. 合法性检查if(x>=1&&x<=r&&y>=1&&y<=c){// 2. 该位置是否已被翻开if(show[x][y]=='*'){// 3. 判断是不是雷if(mine[x][y]=='0'){size_tcount=GetMineCount(mine,x,y);show[x][y]=(char)(count+'0');// 数字转字符DisplayBoard(show,ROW,COL);win++;}else{printf("很遗憾,你被炸死了\n");DisplayBoard(mine,ROW,COL);// 展示雷的位置break;}}else{printf("这里已经排查过,请重新输入坐标\n");}}else{printf("请输入合法的坐标\n");}}// 如果 win 达到非雷总数,则胜利if(win==r*c-EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine,ROW,COL);}}

逻辑

  • 玩家输入坐标,先检查是否越界,再检查是否重复翻开。
  • 如果该位置是'0'(无雷),则调用GetMineCount统计周围雷数,并显示在show上。
  • 如果是雷('1'),游戏结束。
  • win等于总格子数减雷数时,表示所有非雷格子全部翻开,胜利。

五、总结

整个代码约 150 行,逻辑清晰,非常适合作为 C 语言初学者的综合练习。如果你能完全理解并自己重写一遍,那么数组、函数、指针(后续扩展)等知识点都会得到巩固,但是这只是最基本的扫雷,真正的扫雷还需要递归,后续可以用各种智能体来改进成带界面的真正的扫雷。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 5:59:29

Unity架构升级:用VContainer依赖注入告别MonoBehaviour面条代码

1. 项目概述&#xff1a;为什么我们要告别 MonoBehaviour&#xff1f;如果你在 Unity 里写过几年代码&#xff0c;大概率会对MonoBehaviour又爱又恨。爱它上手快&#xff0c;一个脚本挂到 GameObject 上就能跑&#xff1b;恨它带来的“面条式”代码——业务逻辑、数据管理、生命…

作者头像 李华
网站建设 2026/8/6 5:57:09

基于OpenClaw AI智能体实现实时司机位置查询的实战教程

1. 项目概述&#xff1a;当AI智能体遇上实时出行最近在折腾一个挺有意思的项目&#xff1a;用OpenClaw这个AI智能体框架&#xff0c;去模拟实现一个类似滴滴打车的核心功能——查询司机位置。这听起来可能有点“跨界”&#xff0c;但背后的逻辑其实非常清晰。我们不是在开发一个…

作者头像 李华
网站建设 2026/8/6 5:55:53

UE4自定义镜头光晕:基于后处理材质实现稳定可控的电影级效果

1. 项目概述&#xff1a;为什么我们需要重新审视UE4的镜头光晕&#xff1f;如果你在UE4项目里用过内置的Lensflare&#xff08;镜头光晕&#xff09;效果&#xff0c;大概率有过这样的体验&#xff1a;在编辑器里调了半天参数&#xff0c;看着预览还行&#xff0c;一进游戏或者…

作者头像 李华
网站建设 2026/8/6 5:55:00

正余弦优化算法(SCA)原理详解与Python工程实践

1. 项目概述&#xff1a;从“正弦波”到“寻优器”的奇妙旅程正余弦优化算法&#xff0c;英文全称Sine Cosine Algorithm&#xff0c;简称SCA。第一次听到这个名字&#xff0c;你可能觉得它和信号处理或者三角函数有关&#xff0c;离我们熟悉的粒子群、遗传算法这些优化算法有点…

作者头像 李华
网站建设 2026/8/6 5:53:49

深入解析常规调幅原理:从载波调制到频谱搬移的通信基石

1. 项目概述&#xff1a;从“调幅”这个老伙计说起如果你对无线电、广播或者老式收音机有过一丝好奇&#xff0c;那么“调幅”这个词你一定不陌生。它全称是“常规调幅”&#xff0c;英文叫Conventional Amplitude Modulation&#xff0c;简称AM。这技术听起来像是上个世纪的古…

作者头像 李华