[课程设计]扫雷

在这里插入图片描述

因为写报告的时候要求注释比较多比较详细吧,所以我就不在这里多余说了,大家直接看代码吧。

提示:代码不要完全复制,我留了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(); //返回字符串类型的数字 
	}
};

猜你喜欢

转载自blog.csdn.net/cls1277/article/details/112645253