前
中
因为写报告的时候要求注释比较多比较详细吧,所以我就不在这里多余说了,大家直接看代码吧。
提示:代码不要完全复制,我留了bug(包括编译错误和逻辑错误哦)
代码直接见后面,仅供参考。
后
#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_NONSTDC_NO_DEPRECATE
#include<iostream>
#include<ctime>
#include<cstdlib>
#include<conio.h>
#include<stdlib.h>
#include<sstream>
#include<string>
#include<iomanip>
#include<queue>
#include<cstring>
#include"Class.cpp"
using namespace std;
int n , flag;
char mp;
bool opp;
string number;
Game mine;
string str(int x) {
stringstream ss;
ss << x; return ss.str();
}
int snum(string s) {
int num;
stringstream ss(s); ss>>num;
return num;
}
void init() {
do{
mine.BuildMap();
}while(mine.ts.size()==0);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
mine.dt[i][j] = "?";
mine.bh[i][j] = str(i * n + j);
}
}
void Rules(int x) {
cout<<"-----------------------------------------------------------------------------------"<<endl;
cout<<"| 这只是一个普通的扫雷游戏,游戏规则如下: |"<<endl;
cout<<"| 几种符号: 【?】未知位置 / 【*】雷 / 【数字】与周围相邻的非雷位置数量 |"<<endl;
cout<<"| 当然,如果地图足够大会很费脑筋,您可以选择输入【T】获取提示 |"<<endl;
cout<<"| 我们用输入一个位置的编号来代替通常扫雷中的点击该位置 |"<<endl;
if(x==2) cout<<"| 下面这个表是每个位置对应的编号 |"<<endl;
cout<<"-----------------------------------------------------------------------------------"<<endl;
}
void devide() {
for(int i=1; i<=5*n; i++) cout<<"=";
cout<<"你的游戏区域";
for(int i=1; i<=5*n; i++) cout<<"=";
cout<<endl;
}
int main() {
while (1) {
opp = false;
system("CLS");
Rules(1);
cout << "在开始游戏之前,请输入地图的大小:" << endl;
cin >> n;
init();
while (1) {
if(!flag) system("CLS");
Rules(2);
mine.Print(mine.bh);
devide();
mine.Print(mine.dt);
if(!flag) {
cout << "请输入一个编号:(输入T获得提示)" << endl;
cin >> number;
}
flag = 0;
if(number!="T"&&(snum(number)>=n*n||snum(number)<0)) continue;
if(number=="T") {
number = mine.tss();
char isc;
cin>>isc;
if(isc=='Y')
flag = 1;
continue;
}
int ttemp = snum(number);
if (mine.isbomb(ttemp) == false) {
mine.bfs(ttemp);
if (mine.ifEnd() == true) {
system("CLS");
cout << "恭喜你!游戏胜利!"<<endl;
mine.Print(mine.map);
break;
}
}
else {
system("CLS");
cout << "你的得分是:" << mine.score() << endl << "很抱歉你踩到雷了。游戏结束!" << endl;
mine.Print(mine.map);
break;
}
}
cout << "还想继续玩吗?(Y/N)" << endl;
do {
cin>>mp;
if(mp=='N'||mp=='Y') {
if(mp=='N') opp = true;
break;
}
cout<<"非法输入,请重新输入:"<<endl;
}while(mp!='N'&&mp!='Y');
if(opp==true)
break;
}
return 0;
}
#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_NONSTDC_NO_DEPRECATE
#include<iostream>
#include<ctime>
#include<cstdlib>
#include<conio.h>
#include<stdlib.h>
#include<sstream>
#include<string>
#include<iomanip>
#include<queue>
#include<cstring>
#include<vector>
extern int n;
#define L 105
using namespace std;
class Game {
public:
vector<int> ts;
string dt[L][L], map[L][L], bh[L][L];
int tsa[L*L];
public:
void BuildMap() {
memset(tsa , 0 , sizeof(tsa));
ts.clear();
srand((unsigned int)time(NULL)); //选择当前时间为随机数种子
double rate = static_cast<double>(rand() % 100) / 100; //产生一个 0-1 之间的随机数 设置为雷的概率
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
double num = static_cast<double>(rand() % 100) / 100; //产生一个 0-1 之间的随机数 与概率比较大小
if (num <= rate) //随机数比概率小的时候设置为雷
map[i][j] = "*"; //标记为雷点
else {
map[i][j] = str(i * n + j); //非雷区域设置为该点的编号
ts.push_back(i*n+j); //ts动态数组保存非雷点的编号
}
}
}
void Print(string x[][L]) {
//形参为字符串数组,方便传参进行多种情况输出
for (int i = 0; i < n; i++) //输出行号下标
cout << " " << setw(5) << i;
cout << endl << " -----"; //输出上面的分割线前半部分
for (int i = 0; i < 10 * (n - 1) + 5; i++) //输出上面的分割线剩余部分
cout << "-";
cout << endl;
for (int i = 0; i < n; i++) {
//输出列号下标和左边的竖线
cout << i << " |";
for (int j = 0; j < n; j++) //输出字符串数组里的内容
cout << setw(5) << x[i][j] << setw(5) << " ";
cout << "|" << endl; //输出右边的竖线
}
cout << " -----"; //输出下面的分割线前半部分
for (int i = 0; i < 10 * (n - 1) + 5; i++) //输出下面的分割线剩余部分
cout << "-";
cout << endl << endl;
}
bool isbomb(int num) {
//num为要判断点的编号
int x = num / n; //根据编号计算出行标和列标
int y = num % n;
if (map[x][y] == "*") //如果是雷 返回true
return true;
return false; //否则 返回false
}
int score() {
int ans = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (dt[i][j] != "?")
ans++;
return ans;
}
bool ifEnd() {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (dt[i][j] == "?" && map[i][j] != "*") //如果存在一个非雷区域没有被找到 返回false
return false;
return true; //所有的非雷区域一定都被找到了 游戏结束返回true
}
void bfs(int from) {
//广搜求与起点相邻的其他非雷点的相邻区域雷的个数
bool vis[L * L]; //vis数组用来判断是否已经被访问过 防止重复入队
memset(vis, false, sizeof(vis)); //默认所有均未被访问
queue<int>q;
q.push(from); //将起点编号入队
vis[from] = true; //把起点设置为已访问
while (!q.empty()) {
//如果队列中还有元素
int u = q.front(), x, y, num, v, ans = 0;
q.pop(); //u是队列队首的元素 取出并将其在队列中删去
tsa[u] = 1;
x = u / n; y = u % n; //根据编号计算得到行标和列标
if (dt[x][y] != "?") continue; //判断该点是否以前被计算过
v = u; //临时存储起点编号
if (y != 0) {
//确保该点左边存在相邻的点
num = v - 1; //左边相邻的点的编号
if (isbomb(num) == true) ans++; //判断左边的点是不是雷并计数
if (num >= 0 && num < n*n && isbomb(num) == false && vis[num] == false) {
vis[num] = true; //保证编号不超出范围 并且该点不是雷 且之前没被搜索过
q.push(num); //将搜索出来满足条件的点入队
}
}
if (y != n - 1) {
//确保该点右边存在相邻的点
num = v + 1; //右边相邻的点的编号
if (isbomb(num) == true) ans++; //判断右边的点是不是雷并计数
if (num >= 0 && num < n*n && isbomb(num) == false && vis[num] == false) {
vis[num] = true; //保证编号不超出范围 并且该点不是雷 且之前没被搜索过
q.push(num);//将搜索出来满足条件的点入队
}
}
num = (x - 1) * n + y; //上边相邻的点的编号
if (isbomb(num) == true) ans++; //判断上边的点是不是雷并计数
if (num >= 0 && num < n*n && isbomb(num) == false && vis[num] == false) {
vis[num] = true; //保证编号不超出范围 并且该点不是雷 且之前没被搜索过
q.push(num); //将搜索出来满足条件的点入队
}
num = (x + 1) * n + y; //下边相邻的点的编号
if (isbomb(num) == true) ans++; //判断下边的点是不是雷并计数
if (num >= 0 && num < n*n && isbomb(num) == false && vis[num] == false) {
vis[num] = true; //保证编号不超出范围 并且该点不是雷 且之前没被搜索过
q.push(num); //将搜索出来满足条件的点入队
}
dt[x][y] = str(ans); //存储该点相邻区域雷的数目
}
}
string tss() {
int sz = ts.size(); //获取存储非雷点编号的动态数组的大小 提高效率
srand((unsigned int)time(NULL)); //选择当前时间为随机数种子
while(1) {
int tsbh = rand()%sz; //生成大小为0-sz的随机数来表示将要提示的点在动态数组中的编号
if(!tsa[ts[tsbh]]) {
//如果没有被玩家找到
cout<<"您得到了一个提示编号:"<<ts[tsbh]<<endl;
cout<<"您是否需要输入这个编号?(Y/N)"<<endl;
return str(ts[tsbh]);
}
}
}
private:
string str(int x) {
stringstream ss; //定义一个字符串流
ss << x; //把数字x放到ss中
return ss.str(); //返回字符串类型的数字
}
};