news 2026/4/12 6:45:46

扫雷游戏的实现(一)初步

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
扫雷游戏的实现(一)初步

C语言常见概念
C 语言:操作符详解——驾驭比特的艺术


文章目录

  • 前言
    • 扫雷游戏简介
      • 历史背景
      • 游戏规则
    • 一.所要实现的简化规则
    • 二.实现流程
      • 1.分文件
      • 2.菜单
      • 3.test函数实现
      • 4.game函数实现
        • 4.1 数据结构的分析
        • 4.2 用数组存放
          • 4.2.1 数组大小
        • 4.3 初始化数组函数的实现
        • 4.4 布置雷的函数实现
        • 4.5 排查雷函数的实现
          • 4.5.1 实现功能--如果这个位置不是雷,就计算这个位置的周围的8个格子有几个雷,并显示出雷的个数
            • 4.5.1.1 实现方法一
          • 4.5.1.2 实现方法二
          • 4.5.2 扫雷游戏通关功能
      • 5. 完整代码如下
      • 6. 扫雷游戏的完善
  • 总结

前言

扫雷,这款诞生于1990年代的经典游戏,曾经是无数人电脑启蒙的必备软件。它简单的规则背后蕴含着深刻的逻辑思维训练价值。作为程序员,用C语言实现扫雷不仅是对经典游戏的致敬,更是一次绝佳的编程实践机会。本文将带你从零开始,用纯粹的C语言实现一个完整的控制台版扫雷游戏。

扫雷游戏简介

历史背景

扫雷最初由微软的Robert Donner和Curt Johnson开发,随Windows 3.1一同发布。它的设计初衷是帮助用户熟悉鼠标操作,特别是右键和左键的配合使用。

游戏规则

  • 游戏目标:在不触发任何地雷的情况下,揭开所有非地雷格子
  • 棋盘组成
    • 隐藏的地雷(通常占总格子的10-20%)
    • 数字格子(显示周围8个格子中的地雷数量)
    • 空白格子(自动展开相邻的空白区域)
  • 操作方式
    • 左键点击:揭开格子
    • 右键点击:标记/取消标记地雷(插旗)
    • 双击:当数字周围标记的地雷数等于该数字时,快速揭开周围格子

本篇文章将简述用C语言实现扫雷游戏的简化版

一.所要实现的简化规则

设置一个9*9的棋盘,安置10个雷。
排查过程如下:
1.如果这个位置是雷,那么游戏结束。
2.如果把不是雷的位置都找出来了,那么游戏结束。
3.如果这个位置不是雷,就计算这个位置的周围的8个格子有几个雷,并显示出雷的个数。

二.实现流程

1.分文件

首先为了代码的简洁性,方便调试,功能责任划分,我们选择建立三个文件,包括一个头文件—game.h,两个源文件—game.c和main.c主程序

采用分模块的编程思想,进行功能划分,把每个功能不一样的单独放在一个c文件里,然后写头文件把它封装成可调用的一个函数,在主函数调用这个封装好的函数,编译的时候一起编译即可

2.菜单

玩一个游戏往往都有一个菜单,扫雷游戏也需要我们用代码打印出一个菜单在控制台上。
如下列代码:

voidmenu(){printf("***************************\n");printf("******** 1. play ********\n");printf("******** 0. exit ********\n");printf("***************************\n");}

给出两个选项play–开始玩 和 exit–退出,方便玩家选择,一目了然

3.test函数实现

为了避免main主函数冗杂,我们用test函数来完成扫雷功能

intmain(){test();return0;}

在实现菜单功能后,玩家通过scanf函数在控制台上给出0和1两种选择,因此我们用switch语句来实现不同选择的不同功能。

voidtest(){intinput=0;do{menu();printf("请选择:>");scanf("%d",&input);//1 0 xswitch(input){case1:printf("start game!\n");break;case0:printf("exit game!\n");break;default:printf("选择错误,请重新选择!\n");break;}}while(input);}

通过do-while语句来完成,如果在控制台上输入的值1和0都不是,那么就 printf(“选择错误,请重新选择!\n”); 重新进入while循环选择

4.game函数实现

总体代码展现

voidgame(intchoice){charmine[ROWS][COLS]={0};//存放雷的信息charshow[ROWS][COLS]={0};//存放显示的信息//初始化棋盘InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');//打印棋盘//DisplayBoard(mine,ROW,COL);//DisplayBoard(show,ROW,COL);//布置雷SetMine(mine,ROW,COL);//打印布置好雷的棋盘//DisplayBoard(mine, ROW, COL);DisplayBoard(show,ROW,COL);//排查雷FindMine(mine,show,ROW,COL);}
4.1 数据结构的分析

扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些
信息。
因为我们需要在99的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个99的数组来存放
信息,也就是创造一个二维数组。
那如果这个位置布置雷,我们就存放1,没有布置雷就存放0.

4.2 用数组存放

初始化两个二维数组,一个存放雷的信息,一个存放显示在屏幕上的信息。

charmine[ROWS][COLS]={0};//存放雷的信息charshow[ROWS][COLS]={0};//存放显示的信息

原因是如我们在棋盘上布置了雷,棋盘上雷的信息(1)和非雷的信息(0),假设我们排查了某
⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录
存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?如果存放在布
置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难。
这⾥我们肯定有办法解决,⽐如:雷和⾮雷的信息不要使⽤数字,使⽤某些字符就⾏,这样就避免冲
突了,但是这样做棋盘上有雷和⾮雷的信息,还有排查出的雷的个数信息,就⽐较混杂,不够⽅便。
这⾥我们采⽤另外⼀种⽅案,我们专⻔给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再
给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到
mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期
排查参考。
同时为了保持神秘,show数组开始时初始化为字符 ‘*’,为了保持两个数组的类型⼀致,可以使⽤同⼀
套函数处理,mine数组最开始也初始化为字符’0’,布置雷改成’1’。

InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');
4.2.1 数组大小

假设我们排查(2,5)这个坐标时,我们访问周围的⼀圈8个⻩⾊位置,统计周围雷的个数是1
假设我们排查(8,6)这个坐标时,我们访问周围的⼀圈8个⻩⾊位置,统计周围雷的个数时,最下⾯的三
个坐标就会越界,为了防⽌越界,我们在设计的时候,给数组扩⼤⼀圈,雷还是布置在中间的99的坐
标上,周围⼀圈不去布置雷就⾏,这样就解决了越界的问题。所以我们将存放数据的数组创建成11
11
是⽐较合适。

#defineROW9#defineCOL9#defineROWSROW+2#defineCOLSCOL+2

同时为了方便修改我们想要玩的扫雷游戏的难度,我们在头文件game.h中#define定义了ROW COL ROWS COLS等常量,将其作为我们设置的二维数组的大小

4.3 初始化数组函数的实现
InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');

初始化的函数实现部分放在game.c文件部分

voidInitBoard(chararr[ROWS][COLS],introws,intcols,charset){inti=0;for(i=0;i<rows;i++){intj=0;for(j=0;j<cols;j++){arr[i][j]=set;}}}

InitBoard函数设置四个参数,分别为要初始化的数组,要初始化的大小,以及初始化为什么字符
通过简单的for双循环来实现遍历每个位置

4.4 布置雷的函数实现
SetMine(mine,ROW,COL);

在game.c文件中实现

voidSetMine(chararr[ROWS][COLS],introw,intcol){intcount=EASY_COUNT;while(count){intx=rand()%row+1;inty=rand()%col+1;if(arr[x][y]=='0'){arr[x][y]='1';count--;}}}

思路:设置一个count变量作为想要布置雷的个数,通过while循环来一个一个布置下去,通过rand函数
来生成布置雷的随机x位置和y位置,下列代码表示随机1-8中的数字。

rand()%row+1rand()%col+1;

且由于用到rand,我们还要在test函数里给出srand函数,设置随机数生成器种子,并设置种子为 time(NULL)(当前时间),因此别忘记包含time.h头文件

srand((unsignedint)time(NULL));

通过if语句,如果该随机位置没有被设为雷,那么将其设为雷,同时count–。

4.5 排查雷函数的实现
FindMine(mine,show,ROW,COL)

同样在game.c文件中实现主体。

voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],introw,intcol){intx=0;inty=0;intwin=0;while(win<row*col-EASY_COUNT){printf("请输入排查雷的坐标:>");scanf("%d %d",&x,&y);//判断坐标是否合法if(x>=1&&x<=row&&y>=1&&y<=col){if(show[x][y]=='*'){if(mine[x][y]=='1'){printf("很遗憾,你踩到雷了,游戏结束!\n");DisplayBoard(mine,ROW,COL);break;}else{intcount=GetMineCount(mine,x,y);show[x][y]=count+'0';DisplayBoard(show,ROW,COL);win++;}}else{printf("该位置已排查,请重新输入!\n");}}else{printf("坐标非法,请重新输入!\n");}}if(win==row*col-EASY_COUNT){printf("恭喜你,排查成功!\n");DisplayBoard(mine,ROW,COL);}}

传递四个参数,同时将两个数组传过去,因为排查雷都需要用到,剩下两个参数为扫雷棋盘大小。
首先给出x和y坐标,先判断其坐标是否合法,在给定棋盘大小范围内,然后通过if-else语句嵌套完成。

4.5.1 实现功能–如果这个位置不是雷,就计算这个位置的周围的8个格子有几个雷,并显示出雷的个数

通过GetMineCount函数实现,将数组,以及需要检查的坐标传过去。

staticintGetMineCount(charmine[ROWS][COLS],intx,inty)

接下来对该坐标周围共8个坐标进行逐一检查。

共有两种代码实现方法

4.5.1.1 实现方法一

通过将周围8个坐标中表示的’0’或’1’字符累加,同时减去8个’0’字符,最终得到雷的个数

staticintGetMineCount(charmine[ROWS][COLS],intx,inty){returnmine[x-1][y]+mine[x-1][y-1]+mine[x][y+1]+mine[x][y-1]+mine[x+1][y-1]+mine[x+1][y]+mine[x+1][y+1]+mine[x-1][y+1]-8*'0';}
4.5.1.2 实现方法二

通过for双循环,遍历8个坐标,并将其累加,得出雷的个数

staticintGetMineCount(charmine[ROWS][COLS],intx,inty){inti=0;intcount=0;for(i=x-1;i<=x+1;i++){intj=0;for(j=y-1;j<=y+1;j++){count+=(mine[i][j]-'0');}}}
4.5.2 扫雷游戏通关功能

此时还未设置扫雷游戏通关,游戏结束的功能。
我们通过设置win这个变量,表示已被排查完的数量,每排查一次,win++一次。直到将棋盘上除了雷的位置,其他都排查完,此时游戏结束

if(win==row*col-EASY_COUNT){printf("恭喜你,排查成功!\n");DisplayBoard(mine,ROW,COL);}

5. 完整代码如下

game.h文件

#pragmaonce#include<stdio.h>#include<stdlib.h>#include<time.h>#defineROW9#defineCOL9#defineROWSROW+2#defineCOLSCOL+2#defineEASY_COUNT10//初始化棋盘voidInitBoard(chararr[ROWS][COLS],introws,intcols,charset);//打印棋盘voidDisplayBoard(chararr[ROWS][COLS],introw,intcol);//布置雷voidSetMine(chararr[ROWS][COLS],introw,intcol);//排查雷voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],introw,intcol);//游戏主函数voidgame(introw,intcol);

game.c文件

#include"game.h"voidInitBoard(charboard[ROWS][COLS],introws,intcols,charset){inti=0;for(i=0;i<rows;i++){intj=0;for(j=0;j<cols;j++){board[i][j]=set;}}}voidDisplayBoard(charboard[ROWS][COLS],introw,intcol){inti=0;printf("--------扫雷游戏-------\n");for(i=0;i<=col;i++){printf("%d ",i);}printf("\n");for(i=1;i<=row;i++){printf("%d ",i);intj=0;for(j=1;j<=col;j++){printf("%c ",board[i][j]);}printf("\n");}}voidSetMine(charboard[ROWS][COLS],introw,intcol){//布置10个雷//⽣成随机的坐标,布置雷intcount=EASY_COUNT;while(count){intx=rand()%row+1;inty=rand()%col+1;if(board[x][y]=='0'){board[x][y]='1';count--;}}}intGetMineCount(charmine[ROWS][COLS],intx,inty){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');}voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],introw,intcol){intx=0;inty=0;intwin=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(mine,ROW,COL);break;}else{//该位置不是雷,就统计这个坐标周围有⼏个雷intcount=GetMineCount(mine,x,y);show[x][y]=count+'0';DisplayBoard(show,ROW,COL);win++;}}else{printf("坐标⾮法,重新输⼊\n");}}if(win==row*col-EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine,ROW,COL);}}

main.c文件

#include"game.h"voidmenu(){printf("***********************\n");printf("***** 1. play *****\n");printf("***** 0. exit *****\n");printf("***********************\n");}voidgame(){charmine[ROWS][COLS];//存放布置好的雷charshow[ROWS][COLS];//存放排查出的雷的信息//初始化棋盘//1. mine数组最开始是全'0'//2. show数组最开始是全'*'InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');//打印棋盘//DisplayBoard(mine, ROW, COL);DisplayBoard(show,ROW,COL);//1. 布置雷SetMine(mine,ROW,COL);//DisplayBoard(mine, ROW, COL);//2. 排查雷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;}

6. 扫雷游戏的完善

是否可以选择游戏难度
◦ 简单 99 棋盘,10个雷
◦ 中等 16
16棋盘,40个雷
◦ 困难 30*16棋盘,99个雷
• 如果排查位置不是雷,周围也没有雷,可以展开周围的⼀片
• 是否可以标记雷
• 是否可以加上排雷的时间显示

如果各位读者大佬们感兴趣,可以尝试实现


总结

以上就是本篇博客的所有内容了,感谢所有看到这里的大佬们。让我们继续学习代码,享受代码!

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

IndexTTS-2-LLM WebUI使用手册:新手快速入门操作详解

IndexTTS-2-LLM WebUI使用手册&#xff1a;新手快速入门操作详解 1. 引言 随着人工智能技术的不断演进&#xff0c;语音合成&#xff08;Text-to-Speech, TTS&#xff09;已从机械朗读迈向自然拟人化表达。在众多前沿方案中&#xff0c;IndexTTS-2-LLM 凭借其融合大语言模型&…

作者头像 李华
网站建设 2026/4/7 18:54:08

从项目实战视角聊 C++ 指针:企业开发中避坑与高效应用

一、指针的核心应用场景1. 高性能数据结构实现指针是自定义底层数据结构的核心&#xff0c;用于串联节点、管理内存地址&#xff0c;典型场景包括链表、树、哈希表、内存池等。#include <cstdlib> #include <iostream>// 通用链表节点结构 struct ListNode {void* …

作者头像 李华
网站建设 2026/4/8 15:09:23

呼叫中心语音洞察:用SenseVoiceSmall实现情绪监控

呼叫中心语音洞察&#xff1a;用SenseVoiceSmall实现情绪监控 1. 引言&#xff1a;呼叫中心智能化的下一站——情绪感知 在现代客户服务系统中&#xff0c;呼叫中心不仅是企业与客户沟通的核心渠道&#xff0c;更是客户体验的关键触点。传统的语音识别&#xff08;ASR&#x…

作者头像 李华
网站建设 2026/4/5 12:28:24

NewBie-image-Exp0.1与NovelAI对比:开源动漫生成器评测

NewBie-image-Exp0.1与NovelAI对比&#xff1a;开源动漫生成器评测 1. 引言&#xff1a;开源动漫图像生成的技术演进 近年来&#xff0c;随着扩散模型&#xff08;Diffusion Models&#xff09;在图像生成领域的突破性进展&#xff0c;针对特定风格的专用生成器迅速崛起。其中…

作者头像 李华
网站建设 2026/4/1 15:22:00

YOLOv9性能测评:在CUDA 12.1环境下吞吐量与延迟实测分析

YOLOv9性能测评&#xff1a;在CUDA 12.1环境下吞吐量与延迟实测分析 1. 测试背景与目标 随着实时目标检测在自动驾驶、工业质检和智能安防等场景中的广泛应用&#xff0c;模型推理效率成为决定系统可用性的关键因素。YOLOv9作为YOLO系列的最新演进版本&#xff0c;提出了可编…

作者头像 李华
网站建设 2026/4/2 7:17:44

AI智能文档扫描仪网络配置:跨平台访问端口设置说明

AI智能文档扫描仪网络配置&#xff1a;跨平台访问端口设置说明 1. 引言 1.1 业务场景描述 在现代办公环境中&#xff0c;移动设备拍摄的文档照片常因角度倾斜、光照不均或背景干扰导致难以阅读。传统扫描仪体积大、成本高&#xff0c;而“全能扫描王”类应用多依赖云端处理&…

作者头像 李华