☰
数组和函数实践:扫雷游戏
2026/10/5 6:55:28 网站建设 项目流程

数组和函数实践:扫雷游戏


文章目录

  • 数组和函数实践:扫雷游戏
  • 一.扫雷游戏分析和设计
    • (一)扫雷游戏的功能说明
    • (二)游戏的分析和设计
      • 1.数据结构的分析
      • 2.文件结构设计
  • 二.扫雷游戏的代码实现
    • (一)游戏大致流程
    • (二)`game`函数内容
      • 1.定义`mine`数组和`show`数组
      • 2.初始化棋盘
      • 3.打印棋盘
      • 4.布置雷
      • 5.排雷
    • (三)完整代码

一.扫雷游戏分析和设计

(一)扫雷游戏的功能说明

使用控制台实现经典的扫雷游戏:

  • 游戏可以通过菜单实现继续玩或者退出游戏
  • 扫雷的棋盘默认是9*9的格子
  • 默认随机布置10个雷
  • 可以排查雷
  • 如果位置不是雷,就显示周围有几个雷
  • 如果位置是雷,就炸死,游戏结束
  • 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
    游戏界面:

(二)游戏的分析和设计

1.数据结构的分析

扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以需要一定的数据结构来存储这些信息。
因为需要在9*9的棋盘上布置雷的信息和排查雷,首先想到的就是创建一个9*9的数组来存放信息。

空棋盘

某位置布置雷,就存放1,没有布置雷就存放0。

排雷的假设

假设排查(2,4)这个坐标时,访问周围的一圈8个绿色位置,统计周围雷的个数是1,但是排查(8,5)这个坐标时,访问周围的⼀圈8个绿色位置,统计周围雷的个数时,最下面的三个坐标就会越界,为了防止越界,在设计的时候,给数组扩大一圈,雷还是布置在中间的9*9的坐标上,周围一圈不去布置雷,这样就解决了越界的问题。所以将存放数据的数组创建成11*11是比较合适。

周围加上一圈后的棋盘
再继续分析,在棋盘上布置了雷的信息1和非雷的信息0,假设我们排查了某一个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息。这个雷的个数信息如果存放在布置雷的数组中,雷的信息和雷的个数信息就可能或产生混淆和打印上的困难。

解决方案:专门给一个棋盘(对应一个数组mine)存放布置好的雷的信息,再给另外一个棋盘(对应另外一个数组show)存放排查出的雷的信息。
这样就互不干扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时为了保持神秘,show数组开始时初始化为字符'*',为了保持两个数组的类型一致,可以使用同一套函数处理,mine数组最开始也初始化为字符'0',布置雷改成字符'1'。如下图:

mine数组布置雷后的状态 和 show数组输出初始化的状态
对应的数组应该是:
charmine[11][11]={0};//⽤来存放布置好的雷的信息charshow[11][11]={0};//⽤来存放排查出的雷的个数信息

2.文件结构设计

上一篇函数里介绍了多文件的形式对函数的声明和定义,这里实践一下,设计三个文件:
test.c :写游戏的测试逻辑
game.c :写游戏中函数的实现
game.h :写游戏需要的数据类型和函数声明

二.扫雷游戏的代码实现

(一)游戏大致流程

test.c:

用do_while循环,上来先打印菜单。选1,进入游戏(由后面要写的game函数实现),选0,while的判断表达式的值为0,循环直接结束,退出游戏。

(二)game函数内容

1.定义mine数组和show数组

game.h中:

test.c中:

game.c中:

在game.h头文件中用define定义常量ROW,COL(实际棋盘行和列),ROWS,COLS(加上外围一圈后棋盘行和列),方便test.c,game.c中多次调用,也方便后续修改棋盘大小(直接在define处修改大小,不用一个一个去修改)。直接在test.c,game.h中包含game.h头文件即可使用。
为了更加方便,把stdio.h也包含在game.h中,这样包含game.h的同时也可以包含stdio.h。

技巧:多个.c文件公用的东西(库函数,自定义函数,常量)都放到一个头文件中定义和声明会方便清晰很多。

2.初始化棋盘

  1. 在test.c中的game函数里写调用的函数雏形:
//初始化棋盘InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');

注:初始化的棋盘是加了外围一圈后的棋盘,所以传入行列为:ROWS,COLS。

  1. 在game.c中写函数定义:
//初始化棋盘voidInitBoard(charBoard[ROWS][COLS],intr,intc,charset){for(inti=0;i<r;i++){for(intj=0;j<c;j++){Board[i][j]=set;}}}
  • Board:接收要初始化的数组。
  • r,c:接收要遍历的行数和列数。
  • set:接收要初始化的目标字符。

通过遍历数组的行和列,拿到每个元素并赋给它目标字符完成初始化。

  1. 在game.h中声明函数:
voidInitBoard(charBoard[ROWS][COLS],intr,intc,charset);

3.打印棋盘

  1. 在test.c中的game函数里写调用函数雏形:
//打印棋盘DisplayBoard(show,ROW,COL);//DisplayBoard(mine, ROW, COL);

注:
(1) 打印的棋盘是真实棋盘大小,所以传入行列为:ROW,COL。
(2) 在正式玩游戏时,只显示show棋盘。显示mine棋盘是为了后面调试检查代码。

  1. 在game.c中写函数定义:
//打印棋盘voidDisplayBoard(charBoard[ROWS][COLS],intr,intc){printf("------扫雷游戏------\n");inti=0,j=0;for(j=0;j<=c;j++)//打印列号{printf("%d ",j);}printf("\n");//打印完列号换行for(i=1;i<=r;i++){printf("%d ",i);//打印行号for(j=1;j<=c;j++){printf("%c ",Board[i][j]);}printf("\n");//打印完一行,换行}printf("\n");//整体打完再换行}
  • Board:接收要打印的数组。
  • r,c:接收要遍历的行数和列数。

通过遍历数组的行和列,拿到每个元素并打印。同时要注意打印行号和列号,方便玩游戏时快速找到某位置的行号和列号。

  1. 在game.h中声明函数:
voidDisplayBoard(charBoard[ROWS][COLS],intr,intc);
  1. 打印棋盘效果展示:

4.布置雷

  1. 在test.c中的game函数里写调用函数雏形:
//布置雷SetBoard(mine,ROW,COL);DisplayBoard(mine,ROW,COL);//布雷成功后,打印出来检查

注:是在真实棋盘上布置雷,所以传入行列为:ROW,COL。

  1. 在main函数里生成随机的种子:
srand((unsignedint)time(NULL));

注:
(1)初始化随机数生成器在整个工程里只用调用一次,所以必须写到main函数里。
(2)记得在game.h里包含头文件stdlib.h和time.h。

  1. 在game.c中写函数定义:
//布置雷voidSetBoard(charBoard[ROWS][COLS],intr,intc){intcount=EASY_COUNT;while(count){intx=rand()%r+1;//x的范围:1~rinty=rand()%c+1;//y的范围:1~cif(Board[x][y]=='0')//防止重复布雷{Board[x][y]='1';count--;//每成功布置一个雷,循环-1}}}
  • Board:接收要布雷的数组。
  • r,c:接收真实棋盘的行数和列数。

在game.h中定义布雷常量EASY_COUNT,方便随时修改。
用真实棋盘的行数和列数r,c限制随机坐标x,y的范围,把随即坐标对应的数组元素赋为'1',就成功布置了一个雷。

  1. 在game.h中声明函数和布雷个数:
#defineEASY_COUNT10voidSetBoard(charBoard[ROWS][COLS],intr,intc);
  1. 布雷效果展示:

5.排雷

  1. 在test.c中的game函数里写调用函数雏形:
//排雷FindBoard(mine,show,ROW,COL);
  1. 在game.c中写函数定义:

辅助函数,获得排查位置周围雷的个数:

size_tGetMineCount(charmine[ROWS][COLS],intx,inty)//返回雷的个数一定是正数,所以用无符号整数{return(mine[x-1][y-1]+mine[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])-8*'0';//转换为对应的数字}

计算某位置雷的个数原理:

  • 数字字符 -'0'=该数字字符对应的数字
  • 数字 +'0'=该数字对应的数字字符

把排查位置的周围的8个数字字符转换为数字再相加,得该位置周围雷的个数。
待排查坐标(x,y)周围8个字符的坐标如下图:

//排雷voidFindBoard(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc){intx=0,y=0;intwin=0;//成功排雷的个数while(win<r*c-EASY_COUNT){printf("请输入要排查的坐标:\n");scanf("%d%d",&x,&y);if((x>=1&&x<=r)&&(y>=1&&y<=c)){if(show[x][y]=='*'){if(mine[x][y]!='1'){size_tcount=GetMineCount(mine,x,y);show[x][y]=count+'0';//转换为对应的字符DisplayBoard(show,ROW,COL);win++;}else{printf("很遗憾,你被炸死了\n");DisplayBoard(mine,ROW,COL);//为了让玩家死得瞑目,打印minebreak;//跳出循环}}else{printf("该坐标已排查过,请重新输入\n");}}else{printf("输入的坐标非法,请重新输入\n");}}//来到这里有两种情况,1.炸死了,游戏结束。2.排雷成功if(win==r*c-EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine,ROW,COL);}}
  • mine:接收mine数组,用于排查某位置雷的个数,游戏最终显示。
  • show:接受show数组,用于游戏中的显示。
  • r,c:接收真实棋盘的行列。

把除雷以外剩余的位置作循环次数,每成功排完一个雷,循环次数减一。

  1. 在game.h中声明函数:
voidFindBoard(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc);
  1. 效果展示:
    检查排雷不成功的情况:

    检查排雷成功的情况:为方便,把EASY_COUNT设为80,即只有一个位置不是雷,并打印出mine棋盘来作弊,以便快速检查。

(三)完整代码

game.h:

#include<stdio.h>#include<stdlib.h>#include<time.h>#defineROW9#defineCOL9#defineROWSROW+2#defineCOLSCOL+2#defineEASY_COUNT80voidInitBoard(charBoard[ROWS][COLS],intr,intc,charset);voidDisplayBoard(charBoard[ROWS][COLS],intr,intc);voidSetBoard(charBoard[ROWS][COLS],intr,intc);voidFindBoard(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc);

game.c:

#include"game.h"//初始化棋盘voidInitBoard(charBoard[ROWS][COLS],intr,intc,charset){for(inti=0;i<r;i++){for(intj=0;j<c;j++){Board[i][j]=set;}}}//打印棋盘voidDisplayBoard(charBoard[ROWS][COLS],intr,intc){printf("------扫雷游戏------\n");inti=0,j=0;for(j=0;j<=c;j++)//打印列号{printf("%d ",j);}printf("\n");//打印完列号换行for(i=1;i<=r;i++){printf("%d ",i);//打印行号for(j=1;j<=c;j++){printf("%c ",Board[i][j]);}printf("\n");//打印完一行,换行}printf("\n");//整体打完再换行}//布置雷voidSetBoard(charBoard[ROWS][COLS],intr,intc){intcount=EASY_COUNT;while(count){intx=rand()%r+1;//x的范围:1~rinty=rand()%c+1;//y的范围:1~cif(Board[x][y]=='0')//防止重复布雷{Board[x][y]='1';count--;//每成功布置一个雷,循环-1}}}size_tGetMineCount(charmine[ROWS][COLS],intx,inty)//返回雷的个数一定是正数,所以用无符号整数{return(mine[x-1][y-1]+mine[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])-8*'0';//转换为对应的数字}//排雷voidFindBoard(charmine[ROWS][COLS],charshow[ROWS][COLS],intr,intc){intx=0,y=0;intwin=0;//成功排雷的个数while(win<r*c-EASY_COUNT){printf("请输入要排查的坐标:\n");scanf("%d%d",&x,&y);if((x>=1&&x<=r)&&(y>=1&&y<=c)){if(show[x][y]=='*'){if(mine[x][y]!='1'){size_tcount=GetMineCount(mine,x,y);show[x][y]=count+'0';//转换为对应的字符DisplayBoard(show,ROW,COL);win++;}else{printf("很遗憾,你被炸死了\n");DisplayBoard(mine,ROW,COL);//为了让玩家死得瞑目,打印minebreak;//跳出循环}}else{printf("该坐标已排查过,请重新输入\n");}}else{printf("输入的坐标非法,请重新输入\n");}}//来到这里有两种情况,1.炸死了,游戏结束。2.排雷成功if(win==r*c-EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine,ROW,COL);}}

test.c:

#include"game.h"voidmenu(){printf("-----------------------------------\n");printf("----------- 1.play ---------\n");printf("----------- 2.exit ---------\n");printf("-----------------------------------\n");}voidgame(){//定义两个棋盘数组charmine[ROWS][COLS];//存放雷的信息charshow[ROWS][COLS];//存放排查雷的信息//初始化棋盘InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');//打印棋盘DisplayBoard(show,ROW,COL);//DisplayBoard(mine, ROW, COL);//布置雷SetBoard(mine,ROW,COL);DisplayBoard(mine,ROW,COL);//排雷FindBoard(mine,show,ROW,COL);}intmain(){srand((unsignedint)time(NULL));intinput=0;do{menu();printf("请选择:\n");scanf("%d",&input);switch(input){case1:game();break;case0:printf("退出游戏\n");break;default:printf("输入错误,请重新输入\n");break;}}while(input);return0;}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询