01程序设计:需要的功能
第一步:进入菜单,玩家选择进行游戏或者退出程序
第二步:选择难度,不同难度对应着不同大小的扫雷界面与地雷的个数*
第三步:进行游戏
1️⃣程序根据难度生成一个棋盘,并根据难度在其中随机放置不同数量的地雷。
简单:9*9,10个雷;困难:16*16,40个雷
2️⃣玩家选择位置进行排查。若选择的位置不是雷,显示出周围有几个雷,若周围一定范围内都没有雷,则像windows扫雷一样展开周围的一片区域*。若该位置是雷,则游戏结束。
3️⃣玩家也可以选择位置进行标记,即标记出雷所在的位置*。
4️⃣玩家排查出所有的雷,游戏结束。显示出玩家从游戏开始到游戏结束一共花费的时间*。
第四步:返回菜单
*挑战:即笔者不知道实现方法,或对笔者来说有实现的难度TT
02实现过程
2.1项目新增
新建项目mine_game,在项目中新建game.c以及头文件game.h。
2.2第一个功能:menu()函数
menu()需要实现的功能有:
在程序开始时,打印出菜单界面,询问玩家进行游戏或者退出游戏。
在玩家选择了进行游戏后,询问玩家要玩的游戏的难度。
遇到问题:
在编写好初步的程序框架后,准备进行调试,编译器报错。
在查找问题,并询问chatgpt后,发现问题所在:
我习惯性地将将两个.c分开编译了,由此让编译器无法找到函数的定义部分。将他们放在同一个add_executable中,问题解决。
至此,初步完成了menu()函数,可以做到选择进行或退出游戏。
//run.c
#include "game.h"
int main(){
int MenuCommand;
srand((unsigned int)time(NULL));
//之后用于生成随机数。其所需要的头文件,放在game.h中。
do {
MenuCommand = -1;
menu(MenuCommand);
scanf("%d",&MenuCommand);
switch(MenuCommand) {
case 1:
game();
//game()即是扫雷游戏的主体函数,将函数声明和函数原型放在game.h和game.c中
break;
case 0:
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}while(MenuCommand);
return 0;
}
//game.c
#include"game.h"
void menu(int n)
{
if (n==-1)
{
printf("*************************\n");
printf("****** 1.play ********\n");
printf("****** 0.exit ********\n");
printf("*************************\n");
printf("请选择>");
}
}
2.3 DifChoose()函数
根据选择的难度对棋盘的长宽进行赋值,对mine的个数进行赋值
void DifChoose(void){
int level;
printf("***************************\n");
printf("*****请选择游戏难度**********\n");
printf("***** 11.easy ******\n");
printf("***** 22.hard ******\n");
printf("***************************\n");
printf("请选择>");
scanf("%d",&level);
if (level==11)
{
ROWS = 11;
COLS = 11;
row=9;
col=9;
MineNum=10;
r = 9;
c = 9;
}
if(level==22)
{
ROWS = 18;
COLS = 18;
row=16;
col=16;
MineNum=40;
r = 16;
c = 14;
}
}
2.4 InitBoard()函数
扫雷游戏的主要实现过程是创建两个数组,下层数组用以记录生成的地雷的位置,上层数组用以展示给用户,并且不能直接告诉用户哪里有地雷,因此,两个数组应该相互独立。
由于后面涉及到相邻八个地址是否有雷的计算,因此,下层埋放地雷的数组,应当比上层展示数组多一圈,即是在row和col上都要多2。
Initboard()函数用以初始化数组(后称为棋盘),在下层放雷的棋盘初始化每个都是字符‘0’,方便后续随机布置地雷,上层初始化地址则是每个位置都是初始的‘*’字符。
void InitBoard(char board[ROWS][COLS],int i,int j,char set)
{
for(int x=0;x<i;x++)
{
for(int y=0;y<j;y++)
board[x][y]=set;
}
}
2.5 PlaceMine()函数
PlaceMine()函数大致思路即是在底层棋盘中利用rand()函数生成的伪随机值,用与选择的Mine Num个数一致的‘1’替换掉棋盘上的‘0’。
void PlaceMine(char board[ROWS][COLS],int i,int j )//board是放雷的,i和j是小的排和列
{
int count= MineNum;
while (count)
{
// printf("%d ",count);
int x = rand()%i+1;
int y=rand()%j+1;
if(board[x][y]=='0')
{
printf("%d %d\n",x,y);
board[x][y] = '1';
count--;
}
}
}
遇到问题
在这里,我本来写的是board[r][c],并且r和c只在头文件game.h中声明但未初始化,也并未赋值,在实际调试时发现PlaceMine函数在count为1时开始无限循环,本人百思不得其解,求助老师后,老师告知是r和c引起的。因为在c语言中对未初始化的变量执行操作可能会导致无法预知的结果。
在改为board[ROWS][COLS]后,函数正常执行。
2.6 展示棋盘程序 DisplayBoard()
用户必须要有一个可视化的棋盘才能进行游戏,由此,编写DisplayBoard()函数。
void DisplayBoard(char board[ROWS][COLS], int i, int j)
{
printf("*********MineGame************\n");
int x ;
for (x = 0; x <= i; x++)
{
printf("%d", x);//打印列号
}
printf("\n");
for ( x = 1; x < i; x++)
{
printf("%d", x ); // 打印行号
for (int y = 1; y < j; y++)
{
printf("%c", board[x][y]);
}
printf("\n");
}
}
此函数用以打印上层棋盘,并且有从1开始的列号与行号。由于下层棋盘比上层棋盘多一圈,所以上层棋盘的从1开始的坐标刚好与下层棋盘一一对应,十分精妙。
另外,我还写了一个不打印行号和列号的函数,用以检查PlaceMine()之后的下层棋盘,方便调试。
void No_serialDisplay(char board[ROWS][COLS],int i,int j)//调试用函数,不在头文件中声明
{
printf("Inside No_serialDisplay\n");
for (int x=0;x < i;x++) {
for (int y = 0; y < j; y++) {
printf("%c ", board[x][y]);
}
printf("\n");
}
}
2.7 排查雷程序GetMineCount()与FindMine()
用户在选择了地址进行排查后,如果该位置不是雷,应该展示该位置周围的雷的个数,即是FineMine()函数中会调用GetMineCount()函数,因此,我将两者放在一起。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j){
int x=0;
int y=0;
int win=0;
while(win<row*col-MineNum)
{
printf("输入排查的坐标(x y)");
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);
printf("再玩一把吗?\n");
break;
}
else
{
int count = GetMineCount(mine,x,y);
show[x][y]=count+'0';
DisplayBoard(show,row,col);
win++;
}
}
else
printf("非法坐标,请重新输入>\n");
}
if(win==row*col-MineNum)
{
printf("你赢了!\n");
DisplayBoard(mine,row,col);
printf("再玩一把吗?\n");
}
}
至此,主要函数均已完成,接下来,只需要在game()中正确的调用函数,正确的传参,即可完成。
2.8 game()
void game(void){
DifChoose();//难度选择程序 DifChoose() 并通过此程序选择的难度设置COL等变量的值
char mine_board[ROWS][COLS];
char display_board[ROWS][COLS];
InitBoard(mine_board,ROWS,COLS,'0');
InitBoard(display_board,ROWS,COLS,'*');//InitBoard():棋盘初始化程序:一个底层棋盘记录地雷的坐标,上层棋盘展示给用户
No_serialDisplay(mine_board,ROWS,COLS);
No_serialDisplay(display_board,row,col);
PlaceMine(mine_board,row,col);
//PlaceMine():埋雷程序,地雷放置在底层棋盘中
No_serialDisplay(mine_board,ROWS,COLS);
//写一个No_serialDisplay()函数,用以在编写程序时观察。
DisplayBoard(display_board,row,col);
FindMine(mine_board,display_board,row,col);
//GetMineCount():排查雷时,数周围的雷的个数的程序
//FindMine():找雷程序,中间使用GetMineCount()程序。游戏成功失败由此函数判断
}
03 所有函数代码
至此,程序已经完成,解决打印的小问题后,程序调试完毕。
所有的函数代码:
头文件game.h
#ifndef MINE_GAME_H
#define MINE_GAME_H
#endif //MINE_GAME_H
#include "stdlib.h"
#include "stdio.h"
#include "time.h"
int ROWS;
int COLS;
//代表下层棋盘
int row;
int col;
//代表上层的棋盘
int MineNum;//雷的个数
int r,c;//用以函数传参
void menu(int n);
void game(void);
void DifChoose(void);
void InitBoard(char board[ROWS][COLS],int i,int j,char set);
void PlaceMine(char board[ROWS][COLS],int i,int j);
void DisplayBoard(char board[ROWS][COLS], int i, int j);
int GetMineCount(char board[ROWS][COLS],int x,int y);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
源文件game.c:
#include "game.h"
void menu(int n)
{
if (n==-1)
{
printf("*************************\n");
printf("****** 1.play ********\n");
printf("****** 0.exit ********\n");
printf("*************************\n");
printf("请选择>");
}
}
void DifChoose(void){
int level;
printf("*************************\n");
printf("*****请选择游戏难度*****\n");
printf("***** 11.easy ******\n");
printf("***** 22.hard ******\n");
printf("*************************\n");
printf("请选择>");
scanf("%d",&level);
if (level==11)
{
ROWS = 11;
COLS = 11;
row=9;
col=9;
MineNum=10;
r = 9;
c = 9;
}
if(level==22)
{
ROWS = 18;
COLS = 18;
row=16;
col=16;
MineNum=40;
r = 16;
c = 14;
}
}
void InitBoard(char board[ROWS][COLS],int i,int j,char set)
{
for(int x=0;x<i;x++)
{
for(int y=0;y<j;y++)
board[x][y]=set;
}
}
void No_serialDisplay(char board[ROWS][COLS],int i,int j)//调试用函数,不在头文件中声明
{
printf("Inside No_serialDisplay\n");
for (int x=0;x < i;x++) {
for (int y = 0; y < j; y++) {
printf("%c ", board[x][y]);
}
printf("\n");
}
}
void PlaceMine(char board[ROWS][COLS],int i,int j )//board是放雷的,i和j是小的排和列
{
int count= MineNum;
while (count)
{
// printf("%d ",count);
int x = rand()%i+1;
int y=rand()%j+1;
if(board[x][y]=='0')
{
printf("%d %d\n",x,y);
board[x][y] = '1';
count--;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int i, int j)
{
printf("*********MineGame************\n");
int x ;
for (x = 0; x <= i; x++)
{
printf("%d", x);
}
printf("\n");
for ( x = 1; x < i; x++)
{
printf("%d", x ); // 打印行号
for (int y = 1; y < j; y++)
{
printf("%c", board[x][y]);
}
printf("\n");
}
}
int GetMineCount(char board[ROWS][COLS],int x,int y)
{
return(board[x-1][y]+board[x-1][y-1]+board[x][y-1]+board[x+1][y-1]+board[x+1][y]+
board[x+1][y+1]+board[x][y+1]+board[x-1][y+1]-8*'0');
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j){
int x=0;
int y=0;
int win=0;
while(win<row*col-MineNum)
{
printf("输入排查的坐标(x y)");
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);
printf("再玩一把吗?\n");
break;
}
else
{
int count = GetMineCount(mine,x,y);
show[x][y]=count+'0';
DisplayBoard(show,row,col);
win++;
}
}
else
printf("非法坐标,请重新输入>\n");
}
if(win==row*col-MineNum)
{
printf("你赢了!\n");
DisplayBoard(mine,row,col);
printf("再玩一把吗?\n");
}
}
//++++++++++++++++++++++++++++++++++++++++++++++
void game(void){
DifChoose();//难度选择程序 DifChoose() 并通过此程序选择的难度设置COL等变量的值
char mine_board[ROWS][COLS];
char display_board[ROWS][COLS];
InitBoard(mine_board,ROWS,COLS,'0');
InitBoard(display_board,ROWS,COLS,'*');//InitBoard():棋盘初始化程序:一个底层棋盘记录地雷的坐标,上层棋盘展示给用户
No_serialDisplay(mine_board,ROWS,COLS);
No_serialDisplay(display_board,row,col);
PlaceMine(mine_board,row,col);
//PlaceMine():埋雷程序,地雷放置在底层棋盘中
No_serialDisplay(mine_board,ROWS,COLS);
//写一个No_serialDisplay()函数,用以在编写程序时观察。
DisplayBoard(display_board,row,col);
FindMine(mine_board,display_board,row,col);
//GetMineCount():排查雷时,数周围的雷的个数的程序
//FindMine():找雷程序,中间使用GetMineCount()程序。游戏成功失败由此函数判断
}
源文件run.c:
#include "game.h"
int main(){
int MenuCommand;
srand((unsigned int)time(NULL));
do {
MenuCommand = -1;
menu(MenuCommand);
scanf("%d",&MenuCommand);
switch(MenuCommand) {
case 1:
game();
break;
case 0:
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}while(MenuCommand);
return 0;
}
04 其他
总体而言,扫雷游戏对于初学者而言,是一个难度较大的编程项目,我觉得难度主要在于大量的参数传递,在这个过程中,很容易出现问题,特别是如果要区分游戏难度的话。因为区分游戏难度会让ROW、CO L等参数的值无法在一开始就通过define定义,在后续传参容易出现差错。
另外,展开一片的功能我思考了一下,可能需要通过递归或者遍历来实现。但我尝试着写了一下,导致程序卡死TT。现在我开学了,时间较紧,我会在之后慢慢把展开一片、标记雷、计时的功能补上。
总之,完成了一个对现在的我来说有难度的项目,让我十分满足。
难免有疏漏之处,欢迎大家探讨、指正。