1.扫雷游戏的思维导图
我们先做一个简单的思维导图来帮助我们进行代码的编写。如下图:
我们根据上面的思维导图,一步一步的进行代码编写。
2. 游戏菜单
这里我们需要打印一个游戏菜单,让用户选择是否进行游戏。这里的代码很简单,我们直接定义一个函数menu()专门用来打印这个游戏菜单,因为只是打印菜单并不需要任何的返回值,所以我们给这个函数返回类型设置为void,参数也设置为void。
#include <stdio.h>
void menu(void)
{
printf("******************************");
printf("********* 1.扫雷游戏 *********");
printf("**********0.退出游戏 *********");
printf("******************************");
}
int main()
{
menu();
}
3.用户选择
游戏菜单打印完成以后,用户要选择是否进行游戏,我们读取用户的信息录入,用户输入1则进行游戏,输入0则退出游戏,输入其他数值,让用户重新输入。
int main()
{
int input = 1;
menu();
printf("勇士做出你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("轰轰轰。。。。。。扫雷开始");
break;
case 0:
printf("虽然你放弃了我,但是我还会回来的。");
break;
default:
printf("就两个数你也能打错,再给你一次机会。");
break;
}
}
上面的代码我们测试执行以下看看编译器会不会报错。
我们测试下能正常运行,但是在输入其他的选项的时候,程序直接结束了,这里我们在中间加上循环,判断用户输入的值,我们知道循环的判断条件是为真进入循环,为假则结束循环,执行循环后面的语句,而在C语言中,非零的值即为真,零为假,我们根据C语言的特性,用循环条件语句直接判断用户的输入。
int main()
{
int input = 1;
while (input)
{
menu();
printf("勇士做出你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("轰轰轰。。。。。。扫雷开始\n");
break;
case 0:
printf("虽然你放弃了我,但是我还会回来的。\n");
break;
default:
printf("就两个数你也能打错,再给你一次机会。\n");
break;
}
}
}
我们再次编译上面的代码,看下效果:
OK,编译器没有报错,并且我们测试也没用发现其他问题。
3.进入游戏
上面的代码中,我们为了测试所写的代码是否有问题,我们用printf(“轰轰轰。。。。。。扫雷开始\n”);这段代码替代了游戏的实际内容,这里我们就把游戏的内容定义为函数game()。
game()函数只是为了实现游戏的过程,并不用它的任何返回值,所以这里game()函数的返回值我们也设置为void,参数也是设置为void,实际上这个时候我们也没有什么参数可以传递给函数game()的。
game()函数中,我们要实现几个功能:
- 雷阵的初始化;
- 布置地雷;
- 打印隐藏地雷后的雷阵;
- 判断用户的坐标是否是地雷;
- 显示用户输入的坐标周围地雷个数。
上面的问题我们首先要解决地雷怎么隐藏起来,我们知道,一个雷阵我们可以用一个二维数组表示出来,但是我们还要把这个雷阵隐藏起来,这样的话都放在一个数组中,就显得很混乱,而且也很难实现。那么我们是否能用两个数组来显示呢?数组1专门用来放置地雷,数组2用来隐藏雷阵的真实情况,展现给用户,当用户输入一个坐标后,我们根据数组1的情况,来改变数组2的显示情况,这样不就能完美解决了么?
3.1 雷阵的初始化及雷阵打印
雷阵的初始化分为初始化和布置地雷,这里我们先把整个雷阵设置为0,而隐藏用的数组我们把它初始化为。这里考虑到两个数组的初始化的代码基本一样,而且的类型是字符型,为了减少代码的重复性,增加可读性,我们把两个数组的类型都设置为字符型数组。
我们把初始化数组的函数命名为init_boom函数,设置参数为 数组,数组的下标,数组的上标和需要初始化成什么字符4个参数,我们只是初始化数组,不需要返回值,所以返回值设置成void。
void init_board(char arr[10][10], int x, int y,char set_ch)
{
int i = 0;
for (i = 0; i < x;i++)
{
int j = 0;
for (j = 0; j < y;j++)
{
arr[i][j] = set_ch;
}
}
}
我们把上面的代码放在一个新的源文件game.c中,然后我们在创建一个game.h的头文件。在C中的函数一文中有详细介绍多文件的操作,大家可以自行查阅。
game.h头文件我们用来放函数的声明。我们把下面的代码写入其中:
//初始化棋盘。
void init_board(char arr[10][10], int x, int y, char set_ch);
我们在test.c导入头文件game.h,把隐藏雷阵的数组定义为show_boom[]数组,并用函数init_board()对这两个数组进行初始化。
test.c
void game(void)
{
char boom[10][10] = { '0' };
char show_boom[10][10] = { '0' };
init_board(boom, 10, 10,'0');
init_board(show_boom, 10, 10, '0');
}
为了验证初始化是否达到我们想要的结果,而且后面我们还要打印出隐藏的雷阵,所以我们在写一个打印数组的函数,用来打印雷阵。
game.h中代码如下:
//初始化棋盘。
void init_board(char arr[10][10], int x, int y, char set_ch);
//打印棋盘
void print(char arr[10][10], int x, int y);
game.c中加入如下代码:
void print(char arr[10][10], int x, int y)
{
int i = 0;
for (i = 0; i < x;i++)
{
int j = 0;
for (j = 0; j < y;j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
然后我们在test.c文件中,把game()函数修改如下:
void game(void)
{
char boom[10][10] = { '0' };
char show_boom[10][10] = { '0' };
init_board(boom, 10, 10,'0');
init_board(show_boom, 10, 10, '*');
print(boom, 10, 10);
print(show_boom, 10, 10);
}
测试运行看下效果:
初始化没有问题,但是打印出来的效果少点什么,用户要输入相对应的坐标,总不能让用户一个一个数吧,所以我们给显示出来行号列号,对我们自定义的print()函数做如下修改:
void print(char arr[10][10], int x, int y)
{
int i = 0;
//打印列号
for (i = 0; i <= y; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 0; i < x;i++)
{
int j = 0;
//打印行号
printf("%d ", i);
for (j = 0; j < y;j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
上面的代码,我们发现两个数组中的值是一样的,而且如果我们后期想要把雷阵变的更大,就要一个一个更改数组的大小,这样很不方便,这里我们直接在头文件game.h中定义两个常变量:
game.h中添加:
#define ROW 9
#define COM 9
然后我们把相对应的数组地方修改成常变量。
3.2 布置地雷
上面我们已经初始化boom[]数组,我们把boom[]数组中布置的地雷设置成‘1’,用rand()函数生成1到9的数值,然后核对boom[]数组中相对应位置存储的值是否为‘0’,如果是零,我们就改为‘1’,如果不是我们就不修改它。
我们把数组,数组的行数,列数和需要初始化的4个参数传递给函数init_boom()。然后我们在头文件中设置一个常变量BOOMS来表示需要设置的雷的数量。
game.h中添加:
#define BOOMS 10
game.h中添加:
void set_boom(char arr[ROWS][COMS])
{
int i = 0;
int count = BOOMS;
while(count)
{
int arr_row = rand() % ROW + 1;
int arr_com = rand() % COM + 1;
if (arr[arr_row][arr_com] == '0')
{
arr[arr_row][arr_com] = '1';
count--;
}
}
}
我们测试下打印雷阵的效果:
3.3 查找地雷
当用户输入一个坐标以后我们要判断用户坐标周围是否是否有其他的地雷,如果有,我们要做地雷数量的统计,并在用户输入的坐标上显示周围地雷的数量,我们看下面这个9*9的图:
如果用户输入的坐标为当前红色区域的坐标,即[2,2],那么我们要判断红色区域周围绿色区域内是否有地雷,这样是没有错误的但是如果用户输入的坐标是如下的蓝色区域呢?
那我们就要计算第10行还有10列的数值,可是这样越界了啊,编译器会报错的。
那我们把数组扩大一圈,多加一行和一列,如下图:
这样不就不会越界了么?
我们用函数find_boom()查找用户周围坐标的地雷数。然后我们用常变量 ROWS 和COMS来表达扩大后数组的值。
int find_boom(char arr[ROWS][COMS], int x, int y)
{
if (arr[x][y] == '1')
{
return -1;
}
else
{
int count = (arr[x - 1][y - 1] + arr[x][y - 1] + arr[x + 1][y - 1]
+ arr[x - 1][y] + arr[x][y] + arr[x + 1][y]
+ arr[x - 1][y + 1] + arr[x][y + 1] + arr[x + 1][y + 1]) - 8 * '0';
return count;
}
}
上面代码中,如果用户输入的坐标有地雷,那么我们就返回-1。如果没有地雷,那么我们就判断当前坐标周围是否有其他地雷,然后再把这个数值返回出去。
我们在test.c文件中对game()函数做如下修改:
void game(void)
{
char boom[ROWS][COMS] = { '0' };
char show_boom[ROWS][COMS] = { '0' };
init_board(boom, ROWS, COMS,'0');
init_board(show_boom, ROWS, COMS, '*');
set_boom(boom);
print(boom, ROW, COM);
print(show_boom, ROW, COM);
int a = 0;
int b = 0;
printf("请出入一个坐标:");
scanf("%d%d", &a, &b);
int ret = find_boom(boom, a, b);
if (ret >= 0)
{
show_boom[a][b] = (char)ret;
print(show_boom, ROW, COM);
}
else if (ret < 0)
{
printf("很不幸你牺牲了。\n");
print(boom, ROW, COM);
}
}
我们测试下效果:
程序如我们预想的那样输出结果了。我们给这里加上循环,循环的判断 条件我们用打印出来的棋盘的总数减去地雷的数量,如果小于这个数,游戏就继续,如果踩到地雷,那就跳出循环。
好了至此扫雷游戏的代码就全部完成了下面是所有代码展示:
game.h头文件
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 3
#define COM 3
#define ROWS ROW+2
#define COMS ROW+2
#define BOOMS 8
//初始化棋盘。
void init_board(char arr[ROWS][COMS], int x, int y, char set_ch);
//打印棋盘
void print(char arr[ROWS][COMS], int x, int y);
//设置地雷
void set_boom(char arr[ROWS][COMS]);
//查找地雷数
int find_boom(char arr[ROWS][COMS], int x, int y);
game.c源文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化雷阵
void init_board(char arr[ROWS][COMS], int x, int y,char set_ch)
{
int i = 0;
for (i = 0; i < x;i++)
{
int j = 0;
for (j = 0; j < y;j++)
{
arr[i][j] = set_ch;
}
}
}
// 打印雷阵
void print(char arr[ROWS][COMS], int x, int y)
{
int i = 0;
//打印列号
printf(" ");
for (i = 1; i <= y; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= x;i++)
{
int j = 0;
//打印行号
printf("%d ", i);
for (j = 1; j <= y;j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
//设置雷阵
void set_boom(char arr[ROWS][COMS])
{
int i = 0;
int count = BOOMS;
while(count)
{
int arr_row = rand() % ROW + 1;
int arr_com = rand() % COM + 1;
if (arr[arr_row][arr_com] == '0')
{
arr[arr_row][arr_com] = '1';
count--;
}
}
}
int find_boom(char arr[ROWS][COMS], int x, int y)
{
if (arr[x][y] == '1')
{
return -1;
}
else
{
int count = (arr[x - 1][y - 1] + arr[x][y - 1] + arr[x + 1][y - 1]
+ arr[x - 1][y] + arr[x][y] + arr[x + 1][y]
+ arr[x - 1][y + 1] + arr[x][y + 1] + arr[x + 1][y + 1]) - 8 * '0';
return count;
}
}
test.c源文件
#include "game.h"
void menu(void)
{
printf("******************************\n");
printf("********* 1.扫雷游戏 *********\n");
printf("********* 0.退出游戏 *********\n");
printf("******************************\n");
}
void game(void)
{
char boom[ROWS][COMS] = { '0' };
char show_boom[ROWS][COMS] = { '0' };
init_board(boom, ROWS, COMS,'0');
init_board(show_boom, ROWS, COMS, '*');
set_boom(boom);
print(boom, ROW, COM);
print(show_boom, ROW, COM);
int a = 0;
int b = 0;
int count1 = ROW * COM - BOOMS;
while (count1 > 0)
{
printf("请输入一个坐标:");
scanf("%d%d", &a, &b);
int ret = find_boom(boom, a, b);
if (ret >= 0)
{
show_boom[a][b] = (char)ret;
print(show_boom, ROW, COM);
count1--;
}
else if (ret < 0)
{
printf("很不幸你牺牲了。\n");
print(boom, ROW, COM);
break;
}
}
if (count1 == 0)
{
printf("恭喜你,走出雷区。\n");
}
}
int main()
{
int input = 1;
srand((unsigned int)time(NULL));
while (input)
{
menu();
printf("勇士做出你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("虽然你放弃了我,但是我还会回来的。\n");
break;
default:
printf("就两个数你也能打错,再给你一次机会。\n");
break;
}
}
}