扫雷是一款经典的小游戏,那如何使用C语言实现一个扫雷游戏呢?
一、全部源码
直接把全部源码放在开头,如有需要,直接拿走。
源码分为三个文件:
test.cpp/c
主函数的位置
cpp
#include "game.h"
int main()
{
int deep[ROW][COL];//深层,用来记录布置好的地雷和数字,及埋藏在下层的内容
char face[ROW][COL];//表面,用来打印和记录玩家操作,表层,对玩家操作进行记录
int menu1 = 0;//外层菜单,0为退出游戏,1为进入游戏
int menu2 = 0;//内层菜单,0为返回上一步,1为简单难度,2为中等难度,3为大师难度
do
{
GameMenu(1);//外菜单打印
scanf("%d", &menu1);//接收选择
switch(menu1)
{
case 0://退出游戏
printf("你已退出游戏!\n");
break;
case 1://内层菜单,选择难度
GameMenu(2);//内层菜单打印
scanf("%d", &menu2);//接收选择
switch(menu2)
{
case 0://返回上一界面
break;
case 1:
GameEasy(deep, face);//简单难度
break;
case 2:
GameMedium(deep, face);//中等难度
break;
case 3:
GameExpert(deep, face);//大师难度
break;
case 4:
GameNightmare(deep, face);//噩梦难度
break;
default:
printf("ERROR!\n");
break;
}
break;
default :
printf("ERROR!\n");
break;
}
} while (menu1);//只要非零就一直循环,进行游戏,为0则退出游戏
return 0;
}
gane.cpp/c
几乎所有游戏功能函数的位置
cpp
#include "game.h"
//用来打印菜单
void GameMenu(int x)
{
switch(x)
{
case 1://外层菜单
printf("----------------------------\n");
printf("------ 1 for start ---------\n");
printf("------ 0 for exit ----------\n");
printf("----------------------------\n");
break;
case 2://内层菜单
printf("----------------------------\n");
printf("------ 1 for Easy ----------\n");
printf("------ 2 for Medium --------\n");
printf("------ 3 for Expert --------\n");
printf("------ 4 for Nightmare -----\n");
printf("------ 0 for back ----------\n");
printf("----------------------------\n");
break;
default :
printf("ERROR!\n");
break;
}
}
//用来初始化深层数组,difficulty用来传难度,不同难度初始化大小不同
void DeepBoardIntial(int deep[ROW][COL], int difficulty)
{
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
deep[i][j] = 0;//初始化为0,以便之后填充数字
}
}
}
//用来初始化表面数组,difficulty用来传难度,不同难度初始化大小不同
void FaceBoardIntial(char face[ROW][COL],int difficulty)
{
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
face[i][j] = '_';//初始化为 _ ,表示未揭示的格子
}
}
}
//打印内容,对应打印可视化面板,这个是打印内容
void ContentPrint(int deep[ROW][COL], char face[ROW][COL], int i, int j)
{
if (face[i][j] == 'x')//只要是玩家揭示的,直接打印deep中的内容(如果是炸弹,通过IsGameWin函数判断,就直接失败了,不会打印)
{
printf(" %d |", deep[i][j]);
}
else if (face[i][j] == 'L')//这里实现的是连续揭示,原版的一种操作,连续解释的赋值为 L ,方便实现当玩家探索一个没有雷的空块时,程序应该自动揭示与该空块相邻的所有空块,直到遇到数字(即与雷相邻的块)。这个特性是原版扫雷游戏的关键特点,加快游戏进程
{
if (deep[i][j] == 0)//只要数字是零,则直接打印空格,实现原版的周围八个格没有雷则打印空格
{
printf(" |");
}
else
{
printf(" %d |", deep[i][j]);//数字不是零则打印这个数字
}
}
else
{
printf(" %c |",face[i][j]);//没有揭示的格子直接打印 _ 表示未揭示的格子
}
}
//打印可视化面板
void BoardPrint(int deep[ROW][COL], char face[ROW][COL],int difficulty)
{
int i = 0, j = 0;
printf("|");
for (j = 0; j < difficulty; j++)
{
printf("(%-2d|", j + 1);
}
printf("\n");
printf("|");
for (j = 0; j < difficulty; j++)
{
printf("___|");
}
printf("___");
printf("\n");
for (i = 0; i < difficulty; i++)
{
printf("|");
for (j = 0; j < difficulty; j++)
{
ContentPrint(deep,face,i,j);//只有这个是内容打印,其他是打印表格,为了更加美观
}
printf("(%-2d", i + 1);
printf("\n");
printf("|");
for (j = 0; j < difficulty; j++)
{
printf("___|");
}
printf("___");
printf("\n");
}
}
//随即放置地雷
void RandomMineGenerate(int deep[ROW][COL],int difficulty,int minesnum)
{
int x = 0;
int y = 0;
int i = 0;
srand((unsigned int)time(NULL));//设置随机种子
for(i = 0;i < minesnum;i++)
{
x = rand() % difficulty;//产生0~8的数字,表示随机的行
y = rand() % difficulty;//产生0~8的数字,表示随机的列
if(deep[x][y] != MINE)//确保这个位置不是雷,只要不是已经是地雷的格子,则放一个地雷,防止重复放置导致的地雷数目减少
{
deep[x][y] = MINE;//将这个随机生成的坐标设为雷
}
else//如果是雷,则重新生成
{
i--;//i--可以实现此次作废,再来生成一次的效果
}
}
}
//检查是否胜利,0为输,1为赢,2为继续
int IsGameWin(int deep[ROW][COL], char face[ROW][COL], int row, int col, int difficulty)
{
if (deep[row][col] == MINE && face[row][col] == 'x')//只要是雷且玩家点击,返回0,即输
{
return 0;
}
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
if (deep[i][j] != MINE && face[i][j] == '_')//只要还有非雷格子没探索,返回2,就继续
{
return 2;
}
}
}
return 1;//没有踩雷,且没有非雷格子未探索,返回1,则赢
}
//连续揭示,点的是周围没有雷的格子,即数字零,周围就会连续揭示,实现原版操作
//连续揭示,原版的一种操作,连续解释的赋值为 L ,方便实现当玩家探索一个没有雷的空块时,程序应该自动揭示与该空块相邻的所有空块,直到遇到数字(即与雷相邻的块)。这个特性是原版扫雷游戏的关键特点,加快游戏进程
void LinkChange(int deep[ROW][COL], char face[ROW][COL], int i, int j,int difficulty)
{
//由于下面使用了递归,必须加入一些条件使得递归不会一直递归下去
if (!CheckIfValid(i, j, difficulty))//只要超出雷区范围,直接返回,函数返回值为void,直接return,不带任何值
{
return;
}
if (face[i][j] == 'L')//已经设置为连续揭示标志 L 的则直接返回
{
return;
}
if (deep[i][j] > 0 && deep[i][j] < 9)//在数字0旁边的非零数字,也设置为 L 并且返回
{
face[i][j] = 'L';
return;
}
if (deep[i][j] == 9)//是地雷则直接返回
{
return;
}
if (deep[i][j] == 0)//一个位置为0,将这个位值设为 L 并且探索它周围八个格子,进行递归
{
face[i][j] = 'L';
LinkChange(deep, face, i - 1, j - 1, difficulty);
LinkChange(deep, face, i - 1, j, difficulty);
LinkChange(deep, face, i - 1, j + 1, difficulty);
LinkChange(deep, face, i, j - 1, difficulty);
LinkChange(deep, face, i, j + 1, difficulty);
LinkChange(deep, face, i + 1, j - 1, difficulty);
LinkChange(deep, face, i + 1, j, difficulty);
LinkChange(deep, face, i + 1, j + 1, difficulty);
}
}
//装填数字,计算非雷格子中应该是数字几,来表示周围雷的个数,装填在数组中
void FillinNum(int deep[ROW][COL],int difficulty)
{
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
if (deep[i][j] == MINE)//如果坐标(i,j)是雷,则将雷旁边的八个格子都加一
{
if (CheckIfValid(i - 1,j - 1,difficulty) && deep[i - 1][j - 1] != MINE )//判断格子是否是雷,并且判断坐标是否有效,格子有效且不是地雷才能加一
{
deep[i - 1][j - 1]++;
}
if (CheckIfValid(i - 1, j, difficulty) && deep[i - 1][j] != MINE)
{
deep[i - 1][j]++;
}
if (CheckIfValid(i - 1, j + 1, difficulty) && deep[i - 1][j + 1] != MINE)
{
deep[i - 1][j + 1]++;
}
if (CheckIfValid(i, j - 1, difficulty) && deep[i][j - 1] != MINE)
{
deep[i][j - 1]++;
}
if (CheckIfValid(i, j + 1, difficulty) && deep[i][j + 1] != MINE)
{
deep[i][j + 1]++;
}
if (CheckIfValid(i + 1, j - 1, difficulty) && deep[i + 1][j - 1] != MINE)
{
deep[i + 1][j - 1]++;
}
if (CheckIfValid(i + 1, j, difficulty) && deep[i + 1][j] != MINE)
{
deep[i + 1][j]++;
}
if (CheckIfValid(i + 1, j + 1, difficulty) && deep[i + 1][j + 1] != MINE)
{
deep[i + 1][j + 1]++;
}
}
}
}
}
//检查坐标是否有效
int CheckIfValid(int row, int col,int difficulty)
{
if (row >= 0 && row < difficulty && col >= 0 && col < difficulty)//检查坐标是否在雷区内
{
return 1;//在雷区内为有效,返回1为有效
}
return 0;//不在雷区内为无效,返回0为无效
}
//简单难度
void GameEasy(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 9);//简单难度格子为 9 * 9 大小
FaceBoardIntial(face, 9);
RandomMineGenerate(deep, 9,9);//随机生成地雷
FillinNum(deep, 9);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 9);//打印
while(1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1,9) && face[row - 1][col - 1] != 'x' && face[row - 1][col - 1] != 'L')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 9) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face,row - 1,col - 1,9);//检查是否胜利或失败以及游戏是否继续
if(check == 2)//只要游戏继续,就判断是否连续揭示
{
if(deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1,9);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if(check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
//中等难度
void GameMedium(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 16);//中等难度格子为 16 * 16 大小
FaceBoardIntial(face, 16);
RandomMineGenerate(deep, 16,32);//随机生成地雷
FillinNum(deep, 16);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 16);//打印
while (1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1, 16) && face[row - 1][col - 1] != 'x' && face[row - 1][col - 1] != 'L')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 16) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face, row - 1, col - 1, 16);//检查是否胜利或失败以及游戏是否继续
if (check == 2)//只要游戏继续,就判断是否连续揭示
{
if (deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1, 16);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if (check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
//大师难度
void GameExpert(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 30);//大师难度格子为 30 * 30 大小
FaceBoardIntial(face, 30);
RandomMineGenerate(deep, 30,90);//随机生成地雷
FillinNum(deep, 30);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 30);//打印
while (1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1, 30) && face[row - 1][col - 1] != 'x' && face[row - 1][col - 1] != 'L')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 30) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face, row - 1, col - 1, 30);//检查是否胜利或失败以及游戏是否继续
if (check == 2)//只要游戏继续,就判断是否连续揭示
{
if (deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1, 30);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if (check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
//噩梦难度
void GameNightmare(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 50);//噩梦难度格子为 50 * 50 大小
FaceBoardIntial(face, 50);
RandomMineGenerate(deep, 50, 250);//随机生成地雷
FillinNum(deep, 50);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 50);//打印
while (1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1, 50) && face[row - 1][col - 1] != 'x' && face[row - 1][col - 1] != 'L')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 50) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face, row - 1, col - 1, 50);//检查是否胜利或失败以及游戏是否继续
if (check == 2)//只要游戏继续,就判断是否连续揭示
{
if (deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1, 50);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if (check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
game.cpp/c
函数声明
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 50//宏定义,方便更改
#define COL 50
#define MINE 9//宏定义,定义9为雷
//菜单打印
void GameMenu(int x);
//深层数组初始化
void DeepBoardIntial(int deep[ROW][COL],int difficulty);
//表面数组初始化
void FaceBoardIntial(char face[ROW][COL],int difficulty);
//连续揭示打印
void LinkChange(int deep[ROW][COL], char face[ROW][COL], int i, int j,int difficulty);
//实际打印实现
void ContentPrint(int deep[ROW][COL], char face[ROW][COL], int i, int j);
//面板打印
void BoardPrint(int deep[ROW][COL], char face[ROW][COL],int difficulty);
//随机生成雷
void RandomMineGenerate(int deep[ROW][COL], int difficulty,int minesnum);
//检查是否胜利
int IsGameWin(int deep[ROW][COL], char face[ROW][COL], int row, int col, int difficulty);
//填入数字
void FillinNum(int deep[ROW][COL], int difficulty);
//检查输入坐标是否有效
int CheckIfValid(int row, int col, int difficulty);
//简单难度
void GameEasy(int deep[ROW][COL], char face[ROW][COL]);
//中等难度
void GameMedium(int deep[ROW][COL], char face[ROW][COL]);
//大师难度
void GameExpert(int deep[ROW][COL], char face[ROW][COL]);
//噩梦难度
void GameNightmare(int deep[ROW][COL], char face[ROW][COL]);
可以自行运行测试,下面有测试图片以及一些问题的解决办法,在在第七大段。
二、面板
要想扫雷,首先要有可视化的面板,能显示相关的信息,例如显示出数字,表示周围八个格子中有几个雷。所以我们先实现面板,同时还要存储数据,存储雷的位置,以及记录玩家操作的位置。所以我们要定义两个数组,一个用来存储雷的位置和数字,一个用来打印面板和存储玩家操作位置。
cpp
int deep[ROW][COL];//深层,用来记录布置好的地雷和数字,及埋藏在下层的内容
char face[ROW][COL];//表面,用来打印和记录玩家操作,表层,对玩家操作进行记录
1、游戏菜单
同时我们还要有游戏菜单供玩家选择难度和退出游戏。两个菜单,一个外层一个内层。
cpp
//用来打印菜单
void GameMenu(int x)
{
switch(x)
{
case 1://外层菜单
printf("----------------------------\n");
printf("------ 1 for start ---------\n");
printf("------ 0 for exit ----------\n");
printf("----------------------------\n");
break;
case 2://内层菜单
printf("----------------------------\n");
printf("------ 1 for Easy ----------\n");
printf("------ 2 for Medium --------\n");
printf("------ 3 for Expert --------\n");
printf("------ 4 for Nightmare -----\n");
printf("------ 0 for back ----------\n");
printf("----------------------------\n");
break;
default :
printf("ERROR!\n");
break;
}
}
同时配合switch语句实现退出或开始游戏与难度选择:
cpp
GameMenu(1);//外菜单打印
scanf("%d", &menu1);//接收选择
switch(menu1)
{
case 0://退出游戏
printf("你已退出游戏!\n");
break;
case 1://内层菜单,选择难度
GameMenu(2);//内层菜单打印
scanf("%d", &menu2);//接收选择
switch(menu2)
{
case 0://返回上一界面
break;
case 1:
GameEasy(deep, face);//简单难度
break;
case 2:
GameMedium(deep, face);//中等难度
break;
case 3:
GameExpert(deep, face);//大师难度
break;
case 4:
GameNightmare(deep, face);//噩梦难度
break;
default:
printf("ERROR!\n");
break;
}
break;
default :
printf("ERROR!\n");
break;
}
2、初始始化面板
在定义了两个数组用来存储数据后,我们还要对这两个数组初始化,初始化的内容是对于整型数组直接初始化为0,方便后面数字打印;字符型数组初始化为 _ 用来代表没有被揭示的地块。
cpp
//用来初始化深层数组,difficulty用来传难度,不同难度初始化大小不同
void DeepBoardIntial(int deep[ROW][COL], int difficulty)
{
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
deep[i][j] = 0;//初始化为0,以便之后填充数字
}
}
}
//用来初始化表面数组,difficulty用来传难度,不同难度初始化大小不同
void FaceBoardIntial(char face[ROW][COL],int difficulty)
{
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
face[i][j] = '_';//初始化为 _ ,表示未揭示的格子
}
}
}
3、面板打印
为了使面板可见,我们还需要一个面板打印函数,并且在两个数组发生变化时也能打印变化过的面板。也就是在玩家操作后数组内容进行了更新,打印的内容也要进行更新。
(1)表格打印
这里只是用来打印表格,使界面更美观。
cpp
//打印可视化面板
void BoardPrint(int deep[ROW][COL], char face[ROW][COL],int difficulty)
{
int i = 0, j = 0;
printf("|");
for (j = 0; j < difficulty; j++)
{
printf("(%-2d|", j + 1);
}
printf("\n");
printf("|");
for (j = 0; j < difficulty; j++)
{
printf("___|");
}
printf("___");
printf("\n");
for (i = 0; i < difficulty; i++)
{
printf("|");
for (j = 0; j < difficulty; j++)
{
ContentPrint(deep,face,i,j);//只有这个是内容打印,其他是打印表格,为了更加美观
}
printf("(%-2d", i + 1);
printf("\n");
printf("|");
for (j = 0; j < difficulty; j++)
{
printf("___|");
}
printf("___");
printf("\n");
}
}
(2)内容打印
这个是打印内容,实现扫雷过程中内容更新后的打印。
cpp
//打印内容,对应打印可视化面板,这个是打印内容
void ContentPrint(int deep[ROW][COL], char face[ROW][COL], int i, int j)
{
if (face[i][j] == 'x')//只要是玩家揭示的,直接打印deep中的内容(如果是炸弹,通过IsGameWin函数判断,就直接失败了,不会打印)
{
printf(" %d |", deep[i][j]);
}
else if (face[i][j] == 'L')//这里实现的是连续揭示,原版的一种操作,连续解释的赋值为 L ,方便实现当玩家探索一个没有雷的空块时,程序应该自动揭示与该空块相邻的所有空块,直到遇到数字(即与雷相邻的块)。这个特性是原版扫雷游戏的关键特点,加快游戏进程
{
if (deep[i][j] == 0)//只要数字是零,则直接打印空格,实现原版的周围八个格没有雷则打印空格
{
printf(" |");
}
else
{
printf(" %d |", deep[i][j]);//数字不是零则打印这个数字
}
}
else
{
printf(" %c |",face[i][j]);//没有揭示的格子直接打印 _ 表示未揭示的格子
}
}
两个函数配合完成打印工作。
三、游戏功能
1、地雷随机生成
cpp
//随即放置地雷
void RandomMineGenerate(int deep[ROW][COL],int difficulty,int minesnum)
{
int x = 0;
int y = 0;
int i = 0;
srand((unsigned int)time(NULL));//设置随机种子
for(i = 0;i < minesnum;i++)
{
x = rand() % difficulty;//产生0~8的数字,表示随机的行
y = rand() % difficulty;//产生0~8的数字,表示随机的列
if(deep[x][y] != MINE)//确保这个位置不是雷,只要不是已经是地雷的格子,则放一个地雷,防止重复放置导致的地雷数目减少
{
deep[x][y] = MINE;//将这个随机生成的坐标设为雷
}
else//如果是雷,则重新生成
{
i--;//i--可以实现此次作废,再来生成一次的效果
}
}
}
利用rand函数随机产生坐标将这个坐标设为地雷。
2、装填数字
根据这个格子周围八个格子有几个地雷,就把这个格子设为数字几。
cpp
//装填数字,计算非雷格子中应该是数字几,来表示周围雷的个数,装填在数组中
void FillinNum(int deep[ROW][COL],int difficulty)
{
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
if (deep[i][j] == MINE)//如果坐标(i,j)是雷,则将雷旁边的八个格子都加一
{
if (CheckIfValid(i - 1,j - 1,difficulty) && deep[i - 1][j - 1] != MINE )//判断格子是否是雷,并且判断坐标是否有效,格子有效且不是地雷才能加一
{
deep[i - 1][j - 1]++;
}
if (CheckIfValid(i - 1, j, difficulty) && deep[i - 1][j] != MINE)
{
deep[i - 1][j]++;
}
if (CheckIfValid(i - 1, j + 1, difficulty) && deep[i - 1][j + 1] != MINE)
{
deep[i - 1][j + 1]++;
}
if (CheckIfValid(i, j - 1, difficulty) && deep[i][j - 1] != MINE)
{
deep[i][j - 1]++;
}
if (CheckIfValid(i, j + 1, difficulty) && deep[i][j + 1] != MINE)
{
deep[i][j + 1]++;
}
if (CheckIfValid(i + 1, j - 1, difficulty) && deep[i + 1][j - 1] != MINE)
{
deep[i + 1][j - 1]++;
}
if (CheckIfValid(i + 1, j, difficulty) && deep[i + 1][j] != MINE)
{
deep[i + 1][j]++;
}
if (CheckIfValid(i + 1, j + 1, difficulty) && deep[i + 1][j + 1] != MINE)
{
deep[i + 1][j + 1]++;
}
}
}
}
}
3、检查是否胜利
cpp
//检查是否胜利,0为输,1为赢,2为继续
int IsGameWin(int deep[ROW][COL], char face[ROW][COL], int row, int col, int difficulty)
{
if (deep[row][col] == MINE && face[row][col] == 'x')//只要是雷且玩家点击,返回0,即输
{
return 0;
}
int i = 0, j = 0;
for (i = 0; i < difficulty; i++)
{
for (j = 0; j < difficulty; j++)
{
if (deep[i][j] != MINE && face[i][j] == '_')//只要还有非雷格子没探索,返回2,就继续
{
return 2;
}
}
}
return 1;//没有踩雷,且没有非雷格子未探索,返回1,则赢
}
检查是否胜利,或者是否失败,以及游戏是否要继续。
4、连续揭示
连续揭示,原版的一种操作,连续解释的赋值为 L ,方便实现当玩家探索一个没有雷的空块时,程序应该自动揭示与该空块相邻的所有空块,直到遇到数字(即与雷相邻的块)。这个特性是原版扫雷游戏的关键特点,可以加快游戏进程。避免玩家只能一个坐标一个坐标探索浪费太多时间。
cpp
//连续揭示,点的是周围没有雷的格子,即数字零,周围就会连续揭示,实现原版操作
//连续揭示,原版的一种操作,连续解释的赋值为 L ,方便实现当玩家探索一个没有雷的空块时,程序应该自动揭示与该空块相邻的所有空块,直到遇到数字(即与雷相邻的块)。这个特性是原版扫雷游戏的关键特点,加快游戏进程
void LinkChange(int deep[ROW][COL], char face[ROW][COL], int i, int j,int difficulty)
{
//由于下面使用了递归,必须加入一些条件使得递归不会一直递归下去
if (!CheckIfValid(i, j, difficulty))//只要超出雷区范围,直接返回,函数返回值为void,直接return,不带任何值
{
return;
}
if (face[i][j] == 'L')//已经设置为连续揭示标志 L 的则直接返回
{
return;
}
if (deep[i][j] > 0 && deep[i][j] < 9)//在数字0旁边的非零数字,也设置为 L 并且返回
{
face[i][j] = 'L';
return;
}
if (deep[i][j] == 9)//是地雷则直接返回
{
return;
}
if (deep[i][j] == 0)//一个位置为0,将这个位值设为 L 并且探索它周围八个格子,进行递归
{
face[i][j] = 'L';
LinkChange(deep, face, i - 1, j - 1, difficulty);
LinkChange(deep, face, i - 1, j, difficulty);
LinkChange(deep, face, i - 1, j + 1, difficulty);
LinkChange(deep, face, i, j - 1, difficulty);
LinkChange(deep, face, i, j + 1, difficulty);
LinkChange(deep, face, i + 1, j - 1, difficulty);
LinkChange(deep, face, i + 1, j, difficulty);
LinkChange(deep, face, i + 1, j + 1, difficulty);
}
}
5、检查坐标是否有效
cpp
//检查坐标是否有效
int CheckIfValid(int row, int col,int difficulty)
{
if (row >= 0 && row < difficulty && col >= 0 && col < difficulty)//检查坐标是否在雷区内
{
return 1;//在雷区内为有效,返回1为有效
}
return 0;//不在雷区内为无效,返回0为无效
}
四、游戏难度部分
1、简单难度
简单难度的格子为9*9大小,有9个地雷。
cpp
//简单难度
void GameEasy(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 9);//简单难度格子为 9 * 9 大小
FaceBoardIntial(face, 9);
RandomMineGenerate(deep, 9);//随机生成地雷
FillinNum(deep, 9);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 9);//打印
while(1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1,9) && face[row - 1][col - 1] != 'x')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 9) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face,row - 1,col - 1,9);//检查是否胜利或失败以及游戏是否继续
if(check == 2)//只要游戏继续,就判断是否连续揭示
{
if(deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1,9);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if(check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
2、中等难度
中等难度大小为16*16,有32个地雷。
cpp
//中等难度
void GameMedium(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 16);//中等难度格子为 16 * 16 大小
FaceBoardIntial(face, 16);
RandomMineGenerate(deep, 16,32);//随机生成地雷
FillinNum(deep, 16);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 16);//打印
while (1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1, 16) && face[row - 1][col - 1] != 'x' && face[row - 1][col - 1] != 'L')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 16) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face, row - 1, col - 1, 16);//检查是否胜利或失败以及游戏是否继续
if (check == 2)//只要游戏继续,就判断是否连续揭示
{
if (deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1, 16);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if (check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
3、大师难度
大师难度格子为30*30,有90个地雷。
cpp
//大师难度
void GameExpert(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 30);//大师难度格子为 30 * 30 大小
FaceBoardIntial(face, 30);
RandomMineGenerate(deep, 30,90);//随机生成地雷
FillinNum(deep, 30);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 30);//打印
while (1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1, 30) && face[row - 1][col - 1] != 'x' && face[row - 1][col - 1] != 'L')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 30) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face, row - 1, col - 1, 30);//检查是否胜利或失败以及游戏是否继续
if (check == 2)//只要游戏继续,就判断是否连续揭示
{
if (deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1, 30);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if (check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
4、噩梦难度
噩梦难度格子为50*50,地雷为250个。
cpp
//噩梦难度
void GameNightmare(int deep[ROW][COL], char face[ROW][COL])
{
DeepBoardIntial(deep, 50);//噩梦难度格子为 50 * 50 大小
FaceBoardIntial(face, 50);
RandomMineGenerate(deep, 50, 250);//随机生成地雷
FillinNum(deep, 50);//装填数字
int row = 0, col = 0;//用来接收玩家输入行列
int check = 0;//检查是否胜利,0为失败,1为胜利,2为继续
do
{
system("cls");//清屏
BoardPrint(deep, face, 50);//打印
while (1)
{
printf("输入行列>");
scanf("%d %d", &row, &col);
if (CheckIfValid(row - 1, col - 1, 50) && face[row - 1][col - 1] != 'x' && face[row - 1][col - 1] != 'L')//有效且是没被揭开的则输入成功
{
face[row - 1][col - 1] = 'x';
break;//输入成功则直接退出循环
}
else if (CheckIfValid(row - 1, col - 1, 50) && (face[row - 1][col - 1] == 'x' || face[row - 1][col - 1] == 'L'))//输入的坐标为已经揭示的格子,则重新输入
{
printf("已经揭示的格子,请不要重复操作!重新输入!\n");
}
else//不在范围内,重新输入
{
printf("范围错误!重新输入!\n");
}
}
check = IsGameWin(deep, face, row - 1, col - 1, 50);//检查是否胜利或失败以及游戏是否继续
if (check == 2)//只要游戏继续,就判断是否连续揭示
{
if (deep[row - 1][col - 1] == 0)//揭示的格子是空格(即格子是0,周围八个格子没有地雷)则尝试连续揭示
{
LinkChange(deep, face, row - 1, col - 1, 50);//对于这个输入的格子进行连续揭示
}
}
} while (check == 2);//只要check为2则继续,则循环继续,则游戏继续,check为1或0(即赢或输)是退出循环
if (check == 1)//check为1时胜利
{
printf("你赢了!\n");
}
else if (check == 0)//check为0时失败
{
printf("你输了!\n");
}
else//错误
{
printf("ERROR!\n");
}
}
五、主函数部分
主函数集成游戏的功能。
cpp
int main()
{
int deep[ROW][COL];//深层,用来记录布置好的地雷和数字,及埋藏在下层的内容
char face[ROW][COL];//表面,用来打印和记录玩家操作,表层,对玩家操作进行记录
int menu1 = 0;//外层菜单,0为退出游戏,1为进入游戏
int menu2 = 0;//内层菜单,0为返回上一步,1为简单难度,2为中等难度,3为大师难度
do
{
GameMenu(1);//外菜单打印
scanf("%d", &menu1);//接收选择
switch(menu1)
{
case 0://退出游戏
printf("你已退出游戏!\n");
break;
case 1://内层菜单,选择难度
GameMenu(2);//内层菜单打印
scanf("%d", &menu2);//接收选择
switch(menu2)
{
case 0://返回上一界面
break;
case 1:
GameEasy(deep, face);//简单难度
break;
case 2:
GameMedium(deep, face);//中等难度
break;
case 3:
GameExpert(deep, face);//大师难度
break;
case 4:
GameNightmare(deep, face);//噩梦难度
break;
default:
printf("ERROR!\n");
break;
}
break;
default :
printf("ERROR!\n");
break;
}
} while (menu1);//只要非零就一直循环,进行游戏,为0则退出游戏
return 0;
}
六、头文件
头文件声明函数。
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 50//宏定义,方便更改
#define COL 50
#define MINE 9//宏定义,定义9为雷
//菜单打印
void GameMenu(int x);
//深层数组初始化
void DeepBoardIntial(int deep[ROW][COL],int difficulty);
//表面数组初始化
void FaceBoardIntial(char face[ROW][COL],int difficulty);
//连续揭示打印
void LinkChange(int deep[ROW][COL], char face[ROW][COL], int i, int j,int difficulty);
//实际打印实现
void ContentPrint(int deep[ROW][COL], char face[ROW][COL], int i, int j);
//面板打印
void BoardPrint(int deep[ROW][COL], char face[ROW][COL],int difficulty);
//随机生成雷
void RandomMineGenerate(int deep[ROW][COL], int difficulty,int minesnum);
//检查是否胜利
int IsGameWin(int deep[ROW][COL], char face[ROW][COL], int row, int col, int difficulty);
//填入数字
void FillinNum(int deep[ROW][COL], int difficulty);
//检查输入坐标是否有效
int CheckIfValid(int row, int col, int difficulty);
//简单难度
void GameEasy(int deep[ROW][COL], char face[ROW][COL]);
//中等难度
void GameMedium(int deep[ROW][COL], char face[ROW][COL]);
//大师难度
void GameExpert(int deep[ROW][COL], char face[ROW][COL]);
//噩梦难度
void GameNightmare(int deep[ROW][COL], char face[ROW][COL]);
七、一些问题和解决办法
1、打印显示问题
在游玩专家难度及以上的难度时,或者在游玩你自己改进的难度时(你可以通过更改宏ROW和COL的大小实现更大的格子范围),会发现打印出现下列状况:
打印出来的格子错位了。
这个问题可以通过全屏控制台解决,若还不行的话,可能是格子范围太大,可以通过使用Ctrl + 鼠标滚轮来对控制台缩放,Ctrl + 鼠标滚轮下滑可以缩放控制台,使得格子复位。在你不能看到完整的雷区时也可以使用这个方法。
2、输入设置
默认输入坐标是:
cpp
scanf("%d %d", &row, &col);
输入坐标时中间要加一个空格或回车分开,例如
或
中间不能加其他字符,加其他字符会出错,你也可以改为:
cpp
scanf("%d,%d", &row, &col);
这样就只能用 , 进行分隔了,不能用其他字符分隔了。