前言
很多C语言初学者在学完数组之后一直找不到巩固的方法,迷迷糊糊地就学下去了。扫雷游戏是最传统也是最有效的实战项目,本文以扫雷游戏为中心,一步步向大家刨析这个游戏的写作逻辑,以便大家参考~
创建项目
为了方便后续的查找与修改,应将代码适当分散,如头文件引用写在game.h文件中,游戏函数写在game.c文件中,测试游戏运行状况写在test.c文件中。故需要创建3个文件于该工程中。
编写测试框架
#include"game.h"
void menu()
{
printf("********************\n");
printf("*******1.play*******\n");
printf("*******0.exit*******\n");
printf("********************\n");
}
int main()
{
int select;
srand((unsigned int)time(NULL));//此处用time函数返回时间戳给srand当作种子,详见3布置雷
do
{
menu();
printf("请选择:>");
scanf("%d", &select);
switch (select)
{
case 1:
printf("----扫雷游戏----\n");
game();
case 0:
printf("----退出游戏----\n");
break;
default:
printf("输入错误,重新输入\n");
}
} while (select);
return 0;
}
重点:
1.用do while循环控制游戏进程(开始/结束)
2.打印菜单
3.用switch语句匹配对应选项
难点:
game函数的编写
需要区分的是,game()函数应写在test.c文件里,而game()函数内部的函数应在game.h文件内部声明并在game.c文件内部定义。
编写game()函数
1.创建数组
用于雷的摆放和棋盘的打印。
//game.h文件下
#include<stdio.h>
#include<stdlib.h>//提前声明,后面要用到用于随机数的获取
#include<time.h>//提前声明,后面用于返回时间戳,后面会介绍
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
运用符号常量定义常数值便于后续修改,定义ROWS和COLS常量是用于后续排查雷时出现越界的情况。原因是排查雷过程中用的是9*9方格进行排查的,当用户输入的坐标位于棋盘边界时会出现三个或五个位置溢出的情况。
//test.c文件下,game()函数内部
char mine[ROWS][COLS];//用于雷的布置
char show[ROWS][COLS];//用于游戏的运行
扫雷游戏所需的四大基础模块函数
1.初始化棋盘—Init_Board
//game.h文件下
void Init_Board(char arr[ROWS][COLS],int rows,int cols,char set)//函数声明
//为方便一次性修改初始化的值,建议如上所示增加参数set
//game.c文件下
void Init_Board(char arr[ROWS][COLS],int rows,int cols,char set)//函数定义
{
int i = 0;
for(i = 0 ; i < rows ; i++)
{
int j = 0;
for(j = 0 ; j <cols ; j++)
{
arr[i][j] = set;//将数组的每个元素都设置为set值
}
}
}
//test.c文件下
Init_Board(mine,ROWS,COLS,'0');//函数调用
Init_Board(show,ROWS,COLS,'*');
2.打印棋盘—Display_Board
//game.h
void Display_Board(char[ROWS][COLS],int row,int col);
//注意要与前面初始化棋盘时的rows和cols区分,加s表示11,没加表示9
//解释一下这里不加s的原因:
//前面我们虽然创建了11*11的数组,但我们是为了方便排查雷才这样设置的
//事实上我们操作的仍然是9*9的方格,所以此处不用加s,表示打印9*9的内容
//game.c
void Display_Board(char arr[ROWS][COLS],int row,int col)
{
int i = 0;
for(i = 0 ; i <= row ; i++)//用于显示x轴坐标
{
printf("%d ",i);
}
printf("\n");//换行
for(i = 1 ; i <= row ; i++)
{
int j = 0;
printf("%d ",i);//用于显示y轴坐标
for(j = 1 ; j <= col ; j++)
{
printf("%c ",arr[i][j]);//注意区分打印的是整形还是字符型
}
printf("\n");
}
}
//有关初始化棋盘时为什么不把mine初始化为整形0的个人解答:
//当mine数组被初始化为整形0时,相应的show数组也应改为整形数组,如此二者才能有联系
// 但这样会导致玩家观察不方便,以及初始化值与附近雷的个数冲突的情况。
//test.c
Display_Board(show,ROW,COL);
//展示给玩家的是show数组,修改的也是show数组,mine数组用于存储雷的位置
3.布置雷—Set_Mine
在此之前我们先了解一下生成随机数的方法。
rand()函数是C语言标准库提供的能够生成伪随机数的库函数,伪随机数的值由初始种子生成,修改初始种子需要用srand()函数,未修改时种子数默认为1,即伪随机数值固定。二者的使用都需要预先包含头文件stdlib.h
为让种子值不断变化,且每次变化都不与前面的值重叠,以达到近似随机的效果,此时我们可以将时间作为srand()函数的种子,此时可以引用time()函数返回当前时间的时间戳,以达到目的。时间戳为当前时间减去1970年1月1日0时0分的秒数值。
综上可得到以下代码
srand((unsigned int)time(NULL));//将时间戳作为种子
int ret = rand() % 9 + 1;//得到1~9的随机数
//game.h
#define EASY_COUNT 10//设置雷的个数,即游戏难度
void Set_Mine(char mine[ROWS][COLS],int row,int col)//此处同样仅对9*9内修改
//game.c
void Set_mine(char mine[ROWS][COLS],int row,int col)
{
int count = EASY_COUNT;
int x = rand() % 9 + 1;//获取1~9间的随机数
int y = rand() % 9 + 1;
for(count)
{
if(mine[x][y] == '0')//mine数组被初始化为'0',该步骤是为了防止同一个位置被设置两个雷
{
mine[x][y] = '1';//把雷设置为'1'
count--;
}
}
}
//test.c
//game()函数内部
Set_Mine(mine,ROW,COL);
//main()函数下
srand((unsigned int)time(NULL));
4.排查雷—Find_Mine
排查原理:当玩家输入的坐标存在雷,则宣告游戏失败,并打印mine数组;当玩家输入的坐标不是雷时就统计该坐标的九宫格内雷的个数,并显示到show棋盘上。
围绕原理,需要重点学习的是统计九宫格内雷的个数。解决方法如下
//game.c
//Find_Mine()
int x = 0, y = 0;//设玩家输入的坐标为(x,y)
scanf("%d%d",&x,&y);//获取玩家输入的坐标信息
利用for循环计算该坐标九宫格内的字符总和,再用总和减去9*'0',得到雷的整形个数
//创建新函数求雷的个数
int Get_Mine_Count(char mine[ROWS][COLS],int x,int y)
{
int i = 0 , count = 0;
for(i = x - 1 ; i <= x + 1 ; i++)
{
int j = 0;
for(j = y - 1 ; j <= y + 1 ; j++)
{
count += mine[i][j];
}
}
return count - 9*'0';//注意此处返回的是整形的雷的数目,不是字符
}
//原理分析:
//我们知道'0'的ASCII码值为48,'1'的ASCII码值为49,依次类推,即数字的ASCII码值相邻
//所以'1'-'0' = 1(整形),'2' - '0' = 2;同样的,1 + '0' = '1'
//因此当我们得到整形雷总数时,加上'0'就能得到当前坐标周围雷的个数(字符型)
int ret = Get_Mine_Count(mine,x,y);
show[x][y] = ret + '0';//将统计后雷的个数赋给该坐标的元素,以达到数组间的交换
//这一步使两个数组产生联系,这也就是为什么两个数组必须相同类型的原因
以上是排查雷的重难点,获取雷的个数,接下来将给出Find_Mine()函数的原型,以便大家观察
//game.h文件
void Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0,y = 0;
int win = 0;
while(win <= row*col - EASY_COUNT)//胜利标志是排完所有的雷
{
scanf("%d%d",&x,&y);
if(x >= 1 && x <= row && y >= 1 && y <= col)//防止玩家输入表格外的数字
{
if(mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
Display_Board(mine,ROW,COL);
break;
}
else
{
int ret = Get_Mine_Count(mine,x,y);
show[x][y] = ret + '0';
Display_Board(show,ROW,COL);
win++;
}
}
else
{
printf("非法输入,重新输入\n");
}
}
if(win == row*col - EASY_COUNT)
{
printf("恭喜获胜\n");
Display_Board(mine,ROW,COL);
}
}
//test.c
Find_Mine(mine,show,ROW,COL);//排查雷需要对两个数组进行操作
小结
扫雷游戏的基本框架已经介绍完了,希望对大家有所帮助,下面是游戏源码,大家可以看看
//game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
#define EASY_COUNT 10
void Init_Board(char arr[ROWS][COLS],int rows,int cols,char set);
void Set_Mine(char arr[ROWS][COLS], int row, int col);
void Display_Board(char arr[ROWS][COLS], int row, int col);
void Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void Init_Board(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
void Display_Board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
void Set_Mine(char arr[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
count--;
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
}
}
}
//获取雷的数目
int Get_Mine_Count(char mine[ROWS][COLS],int x, int y)
{
int count = 0;
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
count += mine[i][j];
}
}
return count - 9 * '0';
}
void Find_Mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 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");
Display_Board(mine, ROW, COL);
break;
}
else
{
int ret = Get_Mine_Count(mine, x, y);
show[x][y] = ret + '0';
Display_Board(show, ROW, COL);
win++;
}
}
else
{
printf("非法输入,重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜获胜\n");
Display_Board(mine, ROW, COL);
}
}
//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//测试扫雷游戏运行情况
void menu()
{
printf("********************\n");
printf("*******1.play*******\n");
printf("*******0.exit*******\n");
printf("********************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化棋盘
Init_Board(mine,ROWS,COLS,'0');
Init_Board(show, ROWS, COLS,'*');
//设置雷
Set_Mine(mine, ROW, COL);
//打印棋盘
//Display_Board(mine, ROW, COL);
Display_Board(show,ROW,COL);
//排查雷
Find_Mine(mine, show, ROW, COL);
}
int main()
{
int select;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &select);
switch (select)
{
case 1:
printf("----扫雷游戏----\n");
game();
case 0:
printf("----退出游戏----\n");
break;
default:
printf("输入错误,重新输入\n");
}
} while (select);
return 0;
}