想完成一个扫雷游戏,需要思考以下的因素
游戏界面(老生常谈的事了)
扫雷实则有两个“棋盘”,一个是放置雷的,一个是显示给玩家的
怎么布置随机的地雷
若玩家选择的地方是地雷,该如何进行接下来的程序。若不是雷,怎么表示周围雷的个数?
是雷和不是雷,怎么表示?
游戏菜单
首先,一个游戏应该有菜单,就是给玩家选择是否开始游戏,且需要保证在游戏结束后,跳回菜单,让玩家可以一直玩下去,而不用退出游戏后再进入——需要用到循环语句和条件语句
void menu()
{
printf("******************************\n");
printf("****** 1. play *******\n");
printf("****** 0. exit *******\n");
printf("******************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();//扫雷游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);//若输入0的时候就会退出游戏
return 0;
}
游戏界面
现在就是制作游戏界面,由于扫雷游戏不仅要给玩家展示每一次选择后的结果,而且还需要定下雷
还有界面边界的时候,不需要读取“棋盘”外边的区域,那么怎么解决?
————用两个数组保存,一个数组show保存每次的游戏状态,另一个数组mine保存雷的位置,进行选择时会在mine数组里选择,但反馈的情况会在show里展现给玩家,其次为了解决边界的情况——9*9大的“棋盘”,我们选择用11*11的数组,这样的话边界的区域也能构成一个九宫格进行地雷的排查
初始化数组
void game()
{
char mine[11][11] = { 0 };//存放布置好的雷的信息
char show[11][11] = { 0 };//存放排查出的雷的信息
}
初始化棋盘
现在应该将棋盘初始化,我们应该设想,如果是雷,用什么表示,不是雷,又用什么表示?
除此之外,由于排除一块区域后,我们需要显示周围地雷数,所以我们干脆将雷设置为'1',
不是雷就设置为'0'。且初始化时要对11*11的数组全部设置
InitBoard(mine, 11, 11, '0');//'0'表示不放置雷,初始化时,默认没有雷
InitBoard(show, 11, 11, '*');//'*'表示此处未知
void InitBoard(char board[11][11], int row, int col, char set)//set表示该区域的状态
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)//控制行数
{
for (j = 0; j < col; j++)//控制列数
{
board[i][j] = set;//先设置好每个方块的状态—*
}
}
}
且用11*11的数组,可以减少代码量,超出部分默认为0,设置雷的时候就放在9*9里即可
展示棋盘
在游戏开始到结束,每进行一次步骤,都需要显示一次棋盘给玩家,让玩家知道情况,但只显示9*9的棋盘
DisplayBoard(show, 9, 9);
void DisplayBoard(char board[11][11], int row, int col)//对应整个数组11*11
{
int i = 0;
int j = 0;
printf("---------扫雷游戏-----------\n");
for(int i=0;i<=row;i++)//控制行数
{
for(int j=0;j<=col;j++)//控制列情况
{
printf("%c ",board[i][j]);
}
printf("\n");
}
printf("---------扫雷游戏-----------\n");
}
显示出来的就是这种表格,但是,这样会给玩家进行选择的时候,造成一定程度上的干扰2,那我们是否可以增添行列数,来实现游戏的简化?
void DisplayBoard(char board[ROWS][COLS], int row, int col)//对应整个数组11*11
{
int i = 0;
int j = 0;
printf("---------扫雷游戏-----------\n");
//打印列号
for (i = 0; i <= col; i++) //第一行就是打印列号
{
printf("%d ", i);//打印出的是数字,不是定义成数字
}
printf("\n");
for (i = 1; i <= row; i++)//从第二行开始,就要先打印一个数字,剩下的就打印棋盘状态
{
printf("%d ", i);//先打印数字,剩下的打印状态—*
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);//状态*
}
printf("\n");//空行
}
printf("---------扫雷游戏-----------\n");
}
简化后的,会更加简洁
布置雷
地雷一定是随机的,所以需要随机数的帮忙
随机数怎么布置?——就用rand函数,然后还需要用srand函数来设置时间戳
主函数就变为
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//在主函数里使用,这样每次调用的时候随机值就已经定好
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();//扫雷游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
将时间戳在主函数里设置,那么每次刚开始运行时,随机数就已经被设置好
接着,我们需要布置雷——怎么布置——在mine数组上实现,且仅作用于9*9的数组
//布置雷
SetMine(mine, 9, 9);
void SetMine(char mine[11][11], int row, int col)
{
//布置10个雷
int count = EASY_COUNT;//为什么要设置成EASY_COUNT?——因为宏定义里,EASY_COUNT就是雷的个数—10
while (count)
{
//生成随机的下标,11*11--有效部分就是1-9
int x = rand()%row+1;//rand%9--0-8 +1 --> 1-9
int y = rand()%col+1;
if (mine[x][y] == '0')//如果该地方没有雷,则放置,放置反复放置雷
{
mine[x][y] = '1';
count--;
}
}
}
我们这里将雷的个数宏定义成EASY_COUNT,原因在后面讲
因为随机数是0-32767,且膜上9后,结果必定是0-8,加上1后就是1-9
且每次设置前,需要判断该地方是否已经设置过。若设置过,则进行又一轮的循环
排查雷
设置好雷后,我们需要进行排查,选定一个区域
选的区域是雷,则提示游戏失败并且结束游戏
选的区域不是雷,则统计周围雷的个数,并且打印出来
与其同时,输入的是11*11的数组,输出的是9*9的数组
判断时用mine数组判断,反馈情况用show数组表示出来
//排查雷
FindMine(mine, show, 9, 9);
void FindMine(char mine[11][11], char show[11][11], int row, int col)
{
//1. 输入排查的坐标
//2. 检查坐标处是不是雷
// (1) 是雷 - 很遗憾炸死了 - 游戏结束
// (2) 不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续
int x = 0;
int y = 0;
int win = 0;
while (win<row*col- EASY_COUNT)//最多行走71次,因为有10个雷
{
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);//x--(1,9) y--(1,9)
//判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')//如果该位置是雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, row, col);//展示一次游戏
break;//停止循环跳出
}
else//若不是雷
{
//不是雷情况下,统计x,y坐标周围有几个雷
int count = get_mine_count(mine, x, y);
show[x][y] = count+'0';
//mine函数里的情况会反馈给show,再用show,展示出来
//然后且有static函数来保留每次的结果
//会将常数转化为对应的数字字符
//字符加上'0'就会转化为相对的数字
//显示排查出的信息,展示棋盘
DisplayBoard(show, row, col);
win++;
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)//排查次数为71次的时候且没有碰到雷,则胜利
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, row, col);
}
}
那我们怎么统计雷的个数呢?前面提到“是雷为1,不是则为0”,那么周围有3个雷,那么反馈的就是3,所以雷的个数就是选定区域周围八个区域的数字和
且,我们每次输出后,就需要保留当时展现的棋盘的状况,因此需要一个函数
——static函数,具体用法可以用msdn或者csdn上搜索
static int get_mine_count(char mine[11][11], int x, int y)
{
return mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0';//一个字符对应一个数字,所以需要乘'0'
}
重点是:由于数组里存放的是字符,那么我们可以用字符-'0'的形式来使字符转化为数字!!!!!!!!
实现多格的扫雷
上述显示的是9*9的情况,那如果我们想实现11*11,10*10,20*20,且改变雷的个数怎么办?
难道要一个一个修改吗?
这时候我们需要用宏定义,设立两个文件game.c(函数的实现),game.h(函数的声明以及宏定义)
给大家展示最后的代码
//'game.h'
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//在宏定义里改变数字,就能轻易地改变雷的数量以及行列数
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//初始化棋盘的
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//'game.c'
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化全部的方块
//为什么是11*11,因为在排查雷的时候,边界排查时会超出数组,为了减少代码量
//直接用11*11,超出部分直接默认为0
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;//先设置好每个方块的状态—*
}
}
}
//仅显示9*9的部分,但接收11*11的数组,多余的地方用来打印行列数
void DisplayBoard(char board[ROWS][COLS], int row, int col)//对应整个数组11*11
{
int i = 0;
int j = 0;
printf("---------扫雷游戏-----------\n");
//打印列号
for (i = 0; i <= col; i++) //第一行就是打印列号
{
printf("%d ", i);//打印出的是数字,不是定义成数字
}
printf("\n");
for (i = 1; i <= row; i++)//从第二行开始,就要先打印一个数字,剩下的就打印棋盘状态
{
printf("%d ", i);//先打印数字,剩下的打印状态—*
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);//状态*
}
printf("\n");//空行
}
printf("---------扫雷游戏-----------\n");
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
//布置10个雷
int count = EASY_COUNT;//为什么要设置成EASY_COUNT?——因为宏定义里,EASY_COUNT就是雷的个数—10
while (count)
{
//生成随机的下标,11*11--有效部分就是1-9
int x = rand()%row+1;//rand%9--0-8 +1 --> 1-9
int y = rand()%col+1;
if (mine[x][y] == '0')//如果该地方没有雷,则放置,放置反复放置雷
{
mine[x][y] = '1';
count--;
}
}
}
//static
//1. 修饰局部变量
//2. 修饰全局变量
//3. 修饰函数
//用static修饰,可以保证每次排查后的数字保存
//且有雷就是1,没有雷就是0,周围的数字相加后就是该地区周围雷的个数
//仅传入有效的部分9*9 超出9*9部分不用管,因为有雷才会是1,统计后的只会是雷的个数,
//超出部分未被定义就是0
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0';//一个字符对应一个数字,所以需要乘'0'
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//1. 输入排查的坐标
//2. 检查坐标处是不是雷
// (1) 是雷 - 很遗憾炸死了 - 游戏结束
// (2) 不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续
int x = 0;
int y = 0;
int win = 0;
while (win<row*col- EASY_COUNT)//最多行走71次,因为有10个雷
{
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);//x--(1,9) y--(1,9)
//判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')//如果该位置是雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, row, col);//展示一次游戏
break;//停止循环跳出
}
else//若不是雷
{
//不是雷情况下,统计x,y坐标周围有几个雷
int count = get_mine_count(mine, x, y);
show[x][y] = count+'0';//会将常数转化为对应的数字字符
//字符加上'0'就会转化为相对的数字
//显示排查出的信息,展示棋盘
DisplayBoard(show, row, col);
win++;
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)//排查次数为71次的时候且没有碰到雷,则胜利
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, row, col);
}
}
//'扫雷'
#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] = {0};//存放布置好的雷的信息
char show[ROWS][COLS] = {0};//存放排查出的雷的信息
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');//'0'表示不放置雷,初始化时,默认没有雷
InitBoard(show, ROWS, COLS, '*');//'*',表示位置
//打印一下棋盘
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//在主函数里使用,这样每次调用的时候随机值就已经定好
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();//扫雷游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
这个扫雷比起我们以往玩过的游戏,还缺少了自动扫出为0区域的功能,所以需要大家可以思考,我会在后些日子发布出来,供大家参考讨论交流
祝:除夕快乐