目录
想必大家对扫雷这款小游戏都不陌生吧,不说它承载了儿时的回忆吧,再怎么说也算是儿时的娱乐方式之一吧。学完数组过后,我想我们有能力用c语言实现一个简易版的扫雷小游戏啦。顺便也学学模块化开发的思想。废话不多说咱直接开干。
开始前的准备
我们需要在visual stdio里面创建如下三个文件:1:test.c 2:game.c 3:game.h 如下图:
游戏部分
打印菜单
我们运行了程序肯定得让用户看到咱游戏开始和结束的方式撒(我的菜单跟简略,各位伙伴有啥好的想法给我留言哈),通过c语言的printf函数提示用户选择。
//test.c里面的代码
void menu()
{
printf("**********************************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("**********************************\n");
}
确实太简单了,因为这不属于实现游戏功能的部分所以我们将它放到test.c这个文件里面。
咱玩游戏都玩不过瘾嘛,所以我们让游戏的主体函数放到do...while 循环里面,无论干哈的让用户见见咱们的游戏嘛。
//test.c里面的代码
void test()
{
//打印菜单
menu();
int input = 0;
//想玩多少次玩多少次
do
{
//用户输入选项
printf("请选择->");
scanf("%d", &input);
switch (input)
{
case 0:
printf("游戏已退出\n");
break;
case 1:
//游戏的主题函数
game();
break;
default:printf("输入有误,请重新输入\n");
}
} while (input);
}
嘿嘿,这零用来退出不错吧,懒才是我们前进的动力,为了不想多敲代码,就得多思考怎样才能偷懒嘛。
棋盘的设置
emm咱回忆回忆扫雷这个游戏哈,嘶,那格子不就是一个二维数组嘛,聪明如你。咱就用一个二维数组来表示咱的棋盘。咱布置雷的时候要用一个棋盘装雷的信息,排雷的时候也需要装排的信息,所以来看我们需要两个二维数组。
//test.c里面的代码
void game()
{
//创建游戏的数组---装雷
char mine[ROWS][COLS] = { 0 };
//创建游戏的数组---排雷
char show[ROWS][COLS] = { 0 };
}
因为咱通过这个游戏也要学习模块化开发的思想,所以呢这个ROWS和COLS需在game.h里面定义,然后在test.c里面包含game.h就阔以啦。
//game.h里面的代码
//包含头文件,这样的话只要在其他文件里面包含game.h就相当于包含了这些东东啦
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//定义一些符号
#define ROWS 11
#define COLS 11
#define ROW 9
#define COL 9
//雷的个数
#define EASY_VERSION 10
这里简单讲一下自定义头文件吧,自定义头文件啥都可以写,你只要在其他源文件文件里面包含了这个头文件,简单粗暴的理解就向当于把这个头文件里面的代码复制粘贴到了你的那个源文件。头文件里面写的东西其他地方就可以用啦。至于为啥要在里面定义两个不同的行个列,且听下面来讲。
初始化棋盘
初始化棋盘的谜底就是方便我们来安放雷,和排查雷滴,用来区别放了雷和没放雷的位置以及排过雷和没排过雷的位置嘛。
//这属于游戏的部分,函数的实现就放在game.c里面啦
//初始化棋盘
//参数1:棋盘;参数2:行;参数3;列;参数4:初始化的字符
void init_board(char arr[ROWS][COLS], int row, int col, char a)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = a;
}
}
}
然后在game函数里面调用初始化棋盘的函数即可,不同的棋盘传入不同的字符哦!
//test.c里面的
void game()
{
//创建游戏的数组---装雷
char mine[ROWS][COLS] = { 0 };
//创建游戏的数组---排雷
char show[ROWS][COLS] = { 0 };
//初始化棋盘--雷盘
init_board(mine, ROWS, COLS, '0');
//初始化棋盘--排雷盘
init_board(show, ROWS, COLS, '*');
}
注意点:你想要在另一个源文件里面调用函数一定要在头文件里面声明该函数哦!!
//game.h里面声明初始化棋盘的函数,声明函数有分号哦!
//初始化棋盘
void init_board(char arr[ROWS][COLS], int row, int col, char a);
打印棋盘
咱初始化棋盘肯定是为了给玩家看撒,所以需要打印出来嘛,当然雷盘就别打印了哈,等会布置雷了,你还打印雷盘是不是有点侮辱玩家智商了呢?
//game.c里面的代码
//打印棋盘
void print_board(char arr[ROWS][COLS], int row, int col)
{
printf("\n------------ 扫雷游戏 ---------------\n");
int i, j, m;
//打印行号,方便玩家输入坐标排雷
for (i = 0; i <= col; i++)
if (i > 0)
{
printf("%d ", i);
}
else
{
printf("%d ", i);
}
printf("\n");
//打印棋盘数据
for (i = 1; i <= row; i++)
{
printf("%d ", i); //打印列,方便玩家输入坐标排雷
for (j = 1; j <= col; j++)
{
printf(" %c ", arr[i][j]);
if (j < col)
{
printf("|");
}
}
printf("\n");
if (i < row)
{
printf(" ");
for (m = 1; m <= col; m++)
{
printf("---");
if (m < col)
{
printf("|");
}
}
printf("\n");
}
}
printf("\n------------ 扫雷游戏 ---------------\n");
}
调用打印棋盘的函数,同样要在头文件里面声明哦
//test.c里面的代码
void game()
{
//创建游戏的数组
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
//打印棋盘
print_board(show, ROW, COL);
}
这里细心的小伙伴就会发现为什么我们打印的九行就列呢?且听俺来分析,初始化11行11列,对于雷盘来所是为了方便统计一个坐标周围雷的数目,对于排雷盘来说则是不需要转化坐标来显示排雷情况,因为雷的坐标是根据11x11的棋盘获得的,如果排雷盘是9x9的那就会有坐标运算很麻烦是吧?
打印好棋盘的效果图如下:
布置雷
布置雷就是通过改变雷盘二维数组的元素内容来实现雷的布置。
每局雷的布置肯定是随机的撒,这就得实现随机数啦。随机数C语言需要包含stdlib.h和time.h,stdlib.h中的rand()函数能够返回一个随机数,但是每一次大的结果都是一样的(第一次运行程序和第二次运行程序的每组随机数相同)。
我们需要通过改变rand()函数中的一个符号常量来达到随机数的效果(rand()函数是通过数学运算来生成伪随机数的,它这里面的算数运算用到了这个符号常量),这就要用到另一个函数srand(unsigned int seed), 。
这个函数可以改变rand()函数中的那个符号常量,从而实现随机数,但是要求种子(参数seed)是变化的才行呀(变化的seed才能动态改变rand中的符号常量嘛)!
emm只是一想还是只有时间是不断变化的嘞,于是就引入时间戳的概念啦。我们要用time.h里面的time函数。最终代码如下。
//test.c中的代码
void test()
{
//设置变化的种子
srand((unsigned int)time(NULL));
//打印菜单
menu();
int input = 0;
do
{
printf("请选择->");
scanf("%d", &input);
switch (input)
{
case 0:
printf("游戏已退出\n");
break;
case 1:
game();
break;
default:printf("输入有误,请重新输入\n");
}
} while (input);
}
随机数的范围得是[1, 9]才行呀,咱这么干就行:让他对行,列分别取模加1即可,0-8 + 1就是 1- 9嘛
//game.c中的代码
//安放雷
void put_mine(char arr[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
while (count < EASY_VERSION)
{
//随机坐标
x = rand() % row + 1;
y = rand() % col + 1;
//用1代表雷
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count++;
}
}
}
排查雷
用户输入排查的坐标,如果运气差,一来就中雷,今天千万别买彩票哈!!!运气好没踩到雷,判断合法性后,在排雷盘显示周围雷的个数即可。
//game.c中的代码
//排查雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int count = row * col - EASY_VERSION;
//count 为雷的个数
while (count)
{
//提示玩家
printf("请输入您要排查的坐标->");
scanf("%d %d", &x, &y);
//判断合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
判断你运气好不好
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!!\n");
//让你死得瞑目,瞅瞅雷盘
print_board(mine, row, col);
break;
}
//运气好没踩到雷
else
{
//判断输入的点是否被排查过
if (show[x][y] == '*')
{
int num = 0;
//这是统计雷的个数的函数,看下面分析哦
num = num_of_mine(mine, x, y);
show[x][y] = '0' + num;
//count-- 表示排雷成功一个
count--;
if (count == 0)
{
print_board(mine, ROW, COL);
}
else
{
print_board(show, ROW, COL);
}
//这是标记雷的函数等下面分析哦
operate_mine(show, row, col);
}
else
{
printf("您输入的坐标已经被排查过了,请重新输入\n");
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
//count 减为零你就成功了撒
if (count == 0)
{
printf("恭喜你,排雷成功\n");
}
}
统计雷的个数
因为排雷坐标周围0(字符0哦)表示不是雷,1(字符1哦)表示是雷,我们把周围所有坐标的ascii值加起来,然后减去8*字符0的ascii值就得到雷的个数了。嘿嘿,是不是感受到用0代表不是雷,用1代表是雷的好处啦!
//game.c里面的代码
//统计雷的个数
int num_of_mine(char mine[ROWS][COLS], int x, int y)
{
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';
}
标记雷与取消标记
只需要让玩家输入要标记的坐标,判断一下原来的位置没有被排查过,并且未标记,并且坐标合法即可。
//从最后一个函数看哦,我的宝
//game.c里面的代码,上面说的标记雷的函数是最下面的那个
//标记雷
void mark_mine(char arr[ROWS][COLS])
{
int x, y;
printf("请输入您要标记的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (arr[x][y] == '*')
{
arr[x][y] = '!';
}
else
{
printf("您输入的坐标已被排查过了\n");
}
}
else
{
printf("坐标输入非法请重新输入\n");
}
}
//取消标记
void cancel_mine(char arr[ROWS][COLS])
{
int x, y;
printf("请输入您要标记的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (arr[x][y] == '!')
{
arr[x][y] = '*';
}
else
{
printf("您输入的坐标不能标记或已经被标记, 请重新输入\n");
}
}
else
{
printf("坐标非法请重新输入\n");
}
}
//操作雷
void operate_mine(char arr[ROWS][COLS], int row, int col)
{
int a;
do
{
printf("*********************************\n");
printf("*** 1. 标记雷 ******\n");
printf("*** 2. 取消标记 ******\n");
printf("*** 0. 不标记,不取消 *****\n");
printf("*********************************\n");
printf("\n------------ 扫雷游戏 ---------------\n");
scanf("%d", &a);
switch (a)
{
case 0:break;
case 1:mark_mine(arr);
print_board(arr, row, col);
break;
case 2:cancel_mine(arr);
print_board(arr, row, col);
break;
default:printf("输入有误\n");
}
} while (a);
}
完整代码
game.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROWS 11
#define COLS 11
#define ROW 9
#define COL 9
#define EASY_VERSION 10
//初始化棋盘
void init_board(char arr[ROWS][COLS], int row, int col, char a);
//打印棋盘
void print_board(char arr[ROWS][COLS], int row, int col);
//安放雷
void put_mine(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
#include"game.h"
//初始化棋盘
void init_board(char arr[ROWS][COLS], int row, int col, char a)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = a;
}
}
}
//打印棋盘
void print_board(char arr[ROWS][COLS], int row, int col)
{
printf("\n------------ 扫雷游戏 ---------------\n");
int i, j, m;
for (i = 0; i <= col; i++)
if (i > 0)
{
printf("%d ", i);
}
else
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf(" %c ", arr[i][j]);
if (j < col)
{
printf("|");
}
}
printf("\n");
if (i < row)
{
printf(" ");
for (m = 1; m <= col; m++)
{
printf("---");
if (m < col)
{
printf("|");
}
}
printf("\n");
}
}
printf("\n------------ 扫雷游戏 ---------------\n");
}
//安放雷
void put_mine(char arr[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
while (count < EASY_VERSION)
{
//随机坐标
x = rand() % row + 1;
y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count++;
}
}
}
//统计雷的个数
int num_of_mine(char mine[ROWS][COLS], int x, int y)
{
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';
}
//标记雷
void mark_mine(char arr[ROWS][COLS])
{
int x, y;
printf("请输入您要标记的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (arr[x][y] == '*')
{
arr[x][y] = '!';
}
else
{
printf("您输入的坐标已被排查过了\n");
}
}
else
{
printf("坐标输入非法请重新输入\n");
}
}
//取消标记
void cancel_mine(char arr[ROWS][COLS])
{
int x, y;
printf("请输入您要标记的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (arr[x][y] == '!')
{
arr[x][y] = '*';
}
else
{
printf("您输入的坐标不能标记或已经被标记, 请重新输入\n");
}
}
else
{
printf("坐标非法请重新输入\n");
}
}
//操作雷
void operate_mine(char arr[ROWS][COLS], int row, int col)
{
int a;
do
{
printf("*********************************\n");
printf("*** 1. 标记雷 ******\n");
printf("*** 2. 取消标记 ******\n");
printf("*** 0. 不标记,不取消 *****\n");
printf("*********************************\n");
printf("\n------------ 扫雷游戏 ---------------\n");
scanf("%d", &a);
switch (a)
{
case 0:break;
case 1:mark_mine(arr);
print_board(arr, row, col);
break;
case 2:cancel_mine(arr);
print_board(arr, row, col);
break;
default:printf("输入有误\n");
}
} while (a);
}
//排查雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int count = row * col - EASY_VERSION;
while (count)
{
printf("请输入您要排查的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!!\n");
print_board(mine, row, col);
break;
}
else
{
if (show[x][y] == '*')
{
int num = 0;
num = num_of_mine(mine, x, y);
show[x][y] = '0' + num;
count--;
if (count == 0)
{
print_board(mine, ROW, COL);
}
else
{
print_board(show, ROW, COL);
}
operate_mine(show, row, col);
}
else
{
printf("您输入的坐标已经被排查过了,请重新输入\n");
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (count == 0)
{
printf("恭喜你,排雷成功\n");
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void menu()
{
printf("**********************************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("**********************************\n");
}
void game()
{
//创建游戏的数组
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
//打印棋盘
//print_board(mine, ROW, COL);
print_board(show, ROW, COL);
//布置雷区
put_mine(mine, ROW, COL);
//print_board(mine, ROW, COL);
//排查雷
find_mine(mine, show, ROW, COL);
}
void test()
{
//变化的种子
srand((unsigned int)time(NULL));
//打印菜单
menu();
int input = 0;
do
{
printf("请选择->");
scanf("%d", &input);
switch (input)
{
case 0:
printf("游戏已退出\n");
break;
case 1:
game();
break;
default:printf("输入有误,请重新输入\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
好啦,扫雷的简易版到这里就完啦!!!
进阶版推荐
1. 用递归实现一次显示多个没有雷的位置。
2. 使用c语言的图形库EasyX,实现可视化窗口。