前几天写了一个三子棋游戏,如果有兴趣请点击传送门(三子棋游戏的实现(C语言)),而今天要分享的扫雷游戏原理与三子棋大同小异,但个人认为扫雷还是要比三子棋稍微高端一些的,废话不多说,思路奉上:
1.首先需要俩张面板(即俩个二维数组)
- 一张是用来展示给玩家的游戏面板
- 另一张则是用来布雷的面板
2.把面板展示成我们希望看到的样子,所以需要将我们的二维数组初始化成对应的内容
- 把展示给玩家的面板初始化为全※
- 把布雷的面板初始化为全0
- 为了方便统计,用字符’1’表示有雷,用字符’0‘表示无雷’。
3.初始化之后还要把游戏面板打印成我们想看到的样子以展示给玩家
例如打印成这个样子:
4.完成布雷(即在布雷的面板随机放置若干个’1’)
布置完雷之后布雷的面板应该大概是这个样子(下图是15个雷):
5.获取一个不是雷的位置周围雷的数量
- 如果该位置周围无雷,则递归展开游戏面板,提高扫雷效率
- 如果该位置周围有雷,则显示周围雷的数量
6.玩家扫雷
- 玩家踩到雷,则游戏结束
- 未踩雷则继续
- 把不是雷的位置全部扫完,则扫雷成功
大致思路就是上边归纳的几点,那么具体如何实现呢?下面则附上具体源代码(我注释的比较详细,相信大家都能看懂):
mine.h(放置所有的头文件,相关的宏定义及函数声明)
#ifndef _MINE_H_
#define _MINE_H_
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#pragma warning(disable:4996)
#define ROW 12//(其实是10+2,10*10的面板用来进行游戏,定义成12*12则是为了便于统计雷数)
#define COL 12
#define MINE_NUM 15//雷的数量
void Menu();//菜单函数
void Game();//游戏函数
void InitBoard(char show_board[][COL], int row, int col, char elem);//初始化面板函数
void SetMine(char mine_board[][COL], int row, int col);//布雷函数
void ShowBoard(char show_board[][COL], int row, int col);//打印游戏面板
char GetNum(char mine_board[][COL], int x, int y);//获取周围雷的数量
void Extend(char show_board[][COL], char mine_board[][COL], int row, int col, int x, int y);//递归展开函数
int Judge(char show_board[][COL], int row, int col);//判断游戏结果
#endif
main.c(主逻辑及函数调用)
#include "mine.h"
int main()
{
int quit = 0;
do{
int select = 0;
Menu();
scanf("%d", &select);
switch (select){
case 1:
Game();
printf("Game end! Do you want to play again?\n");
break;
case 2:
quit = 1;//跳出while循环,即退出
printf("Bye, and welcome to play next time!\n");
break;
default:
printf("Enter erorr! Please enter again!\n");//输入有误
break;
}
} while (!quit);
system("pause");
return 0;
}
mine.c(具体的函数实现)
#include "mine.h"
void Menu()
{
printf("**********************************\n");
printf("*** Welcome to mine clearance! ***\n");
printf("**********************************\n");
printf("***1.Play! 2.Exit!***\n");
printf("**********************************\n");
printf("Please enter your selection: ");
}
void InitBoard(char show_board[][COL], int row, int col, char elem)//初始化面板
{
int i = 0;
for (; i < row; i++){
int j = 0;
for (; j < col; j++){
show_board[i][j] = elem;//分别传递不同的实参可以分别初始化游戏面板和布雷面板
}
}
}
void SetMine(char mine_board[][COL], int row, int col)//随机布雷函数
{
int n = MINE_NUM;
while (n){//当雷布置完之后结束循环
int x = rand() % (row - 2) + 1;
int y = rand() % (col - 2) + 1;//使随机布的雷在正确的范围内
if (mine_board[x][y] == '1'){
continue;//如果一个位置已经有雷,则重新找一个位置
}
mine_board[x][y] = '1';//布雷
n--;
}
}
void ShowBoard(char show_board[][COL], int row, int col)//打印游戏面板
{
int i = 1;
printf("----");
for (i = 1; i <= col - 2; i++){
printf("%3s", "----");
}
printf("\n");
printf(" |");
for (i = 1; i <= col - 2; i++){
printf(" %-2d|", i);
}
printf("\n");
printf("----");
for (i = 1; i <= col - 2; i++){
printf("%3s", "----");
}
printf("\n");
for (i = 1; i <= row - 2; i++){
printf(" %2d|", i);
int j = 1;
for (; j <= col - 2; j++){
printf(" %-2c|", show_board[i][j]);
}
printf("\n");
for (j = 1; j <= col - 2; j++){
printf("%3s", "----");
}
printf("----\n");
}
}
char GetNum(char mine_board[][COL], int x, int y)//获取周围雷数
{
//根据'1' - '0'= 1可知将这个坐标周围的所有坐标中的内容相加之后再减去8*'0'
//即可得到周围雷的数量(整形),则少减去1个'0'就是该数字对应的字符类型
return mine_board[x - 1][y - 1] + mine_board[x - 1][y] + mine_board[x - 1][y + 1] + \
mine_board[x][y - 1] + mine_board[x][y + 1] + \
mine_board[x + 1][y - 1] + mine_board[x + 1][y] + mine_board[x + 1][y + 1] - 7 * '0';
}
void Extend(char show_board[][COL], char mine_board[][COL], int row, int col, int x, int y)
{
if (GetNum(mine_board, x, y)=='0')//调用GetNum函数,计算周围雷的个数,若为'0',则需要展开
{
show_board[x][y] = ' ';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show_board[i][j] == '*' &&\
i >= 1 && i <= (row-2) && j >= 1 && j <= (col-2))//若该位置未被扫过且在棋盘范围内则继续递归调用Extend函数
Extend(show_board, mine_board, ROW, COL, i, j);
}
}
}
else {
show_board[x][y] = GetNum(mine_board, x, y);//若不需要展开,则为该位置赋值周围雷的个数
}
}
int Judge(char show_board[][COL], int row, int col) //Judge函数,判定用户是否扫雷成功
{
int i = 1;
int j = 1;
int count = 0;
for (; i <= row - 2; i++) {
for (j = 1; j <= col - 2; j++) {
if (show_board[i][j] == '*') {
count++;
}//遍历棋盘元素,若该位置未被扫过,则记录下来。
if (count > MINE_NUM) {
return 0;
}//若未被扫过的个数大于雷的个数,则游戏继续。
}
}
return 1;//说明扫雷成功
}
void Game()
{
srand((unsigned long)time(NULL));//种随机数种子用于布雷
char show_board[ROW][COL];//定义显示给玩家的游戏面板
char mine_board[ROW][COL];//定义显示雷分布的面板
InitBoard(show_board, ROW, COL, '*');//把展示给玩家的游戏面板初始化全'*'
InitBoard(mine_board, ROW, COL, '0'); //把布雷的面板初始化为全'0'(用'0'表示无雷,用'1'表示有雷)
SetMine(mine_board, ROW, COL);//布雷
do{
system("cls");//刷新当前界面,使游戏体验更佳
int x = 0;
int y = 0;
ShowBoard(show_board, ROW, COL);//将游戏面板展示给玩家
printf("Please enter the location you want to sweep<x,y>: ");
scanf("%d %d", &x, &y);
if (x < 1 || x > 10 || y < 1 || y > 10){
printf("Enter erorr!Please enter again!\n");//提示玩家输入的坐标有误
Sleep(2000);//由于界面会刷新,则停顿2秒使玩家看到提示
continue;
}
if (show_board[x][y] != '*'){
printf("The position you entered has been swept!Please enter again!\n");
Sleep(2000);//由于界面会刷新,则停顿2秒使玩家看到提示
continue;//提示玩家此位置已经扫过了
}
if (mine_board[x][y] == '1'){
printf("There is a mine in <%d,%d>!\n", x, y);
printf("Sorry! You lost the game!\n");//扫雷失败
ShowBoard(mine_board, ROW, COL);//失败之后向玩家展示布雷面板,让玩家输的心服口服
break;
}
Extend(show_board, mine_board, ROW, COL, x, y);//没有踩到雷则判断是否需要展开
if (1 == Judge(show_board, ROW, COL)){
printf("Congratulations! You made it!\n");//扫雷成功
ShowBoard(mine_board, ROW, COL);//扫雷成功之后也向玩家展示布雷面板
break;
}
}while(1);
}
运行结果及游戏测试:
开始界面:
游戏过程:
空格部分就是递归展开的部分
游戏结束:
1.中途踩到雷,则扫雷失败:
2.将不是雷的位置全部扫完,则扫雷成功:
有俩点内容我想补充一下:
1.虽然我的扫雷不能直接选择难度,我做的是10*10的游戏面板(布置了15颗雷),但我的代码中所有的关于面板及游戏逻辑的内容都是以表达式的形式写出或者调用的,因此只需手动更改宏定义那里数组行和列的大小以及雷的数量即可更改游戏难度。
例如,将代码中的数组行和列改为:
#define ROW 7//(即5*5的游戏面板)
#define COL 7
#define MINE_NUM 5//雷的数量
再次运行任然不会报错,游戏面板也会随之改变,并且游戏逻辑没有任何bug,大家可以自行尝试
2.我看到有的作者在实现扫雷时写了一个保护机制,即第一次扫雷不会被炸死,原理其实十分简单,如果大家想添加可以自己去实现,但是我觉得没有必要,我的想法是如果你第一次就被炸死那也只能怪自己太倒霉,甚至建议你去买彩票了哈哈(当然如果雷布置的太多则另说)。即使不幸第一次就踩到雷,重新开一局就好了,也没什么大不了的,不至于每次都开局踩雷吧。所以没有添加这部分内容,希望读者能理解我的想法。(我似乎记得当初在windows里玩扫雷也没有这个保护机制吧?)
总结:
总体来说,游戏实现起来并不是很困难,因为逻辑清晰,也没有复杂的语法,但其中细节很多,一不小心就会出错,需要仔细再仔细,而且有些部分需要精雕细刻,比如打印游戏面板的时候其实还是需要费些时间去慢慢调试的。不过,当坚持越过了所有的困难,最终完成的时候,收货还是蛮大的,也有不错的成就感!