基于C++语言实现高效的带异常处理的扫雷游戏程序

目录


前言

        本文将介绍一个功能齐全、高效的带异常处理的扫雷游戏程序。扫雷游戏是一款经典的益智游戏,既考验玩家的思考能力,又增强了集中注意力。本程序采用了C++语言编写,结合了各种技术和特点,使得游戏体验更加流畅,且在输入选点坐标或程序控制选择时避免了可能导致死循环的异常情况。该扫雷程序包含选点后自动展开,底层面板选择性可视化(需要输入密码),内嵌了C++异常处理模块,很好地解决了输入选点坐标或是程序控制选择中接收异常导致程序出现死循环的问题。通过阅读本文,您将了解如何使用C++语言设计一个高效、稳定的扫雷游戏程序,并掌握异常处理的技巧和应用场景。


一、扫雷程序的规则设定

1. 游戏板:创建一个二维数组,用于表示扫雷游戏的游戏板。每个元素代表一个方块,可以是雷或数字。

2. 雷的布置:在游戏板上随机布置一定数量的雷。

3. 方块状态:每个方块都有一个状态,表示它的可见性和标记状态。初始时,所有方块都是隐藏的,玩家需要逐步揭开方块。

4. 方块的相邻关系:每个方块都与其周围的8个方块相邻。这些相邻方块中可能包含雷,用于计算当前方块周围的雷的数量。

5. 游戏操作:玩家可以通过鼠标点击或键盘输入来操作游戏。常见的操作包括判断后揭开方块。

6. 揭开方块:当玩家揭开一个方块时,可以出现以下几种情况:

        1)如果该方块是雷,游戏失败,显示所有雷的位置。
        2)如果该方块周围没有雷,自动揭开周围的相邻方块,直到边界或有雷的方块
        3)如果该方块周围有雷,显示雷的数量

7. 游戏结束条件:当玩家揭开所有非雷方块时,游戏胜利。游戏失败的条件是揭开到一个雷。

8. 用户界面:为了提供良好的用户体验,可以设计一个简单的用户界面,显示游戏板、方块的状态和操作提示。

二、扫雷程序的流程实现分析

下面是程序的逻辑和流程概述:

1. 首先,在`main`函数中,程序创建了一个`Board`对象`board`作为扫雷游戏的游戏板。

2. 然后,程序进入一个循环,显示一个菜单供用户选择。用户可以选择开始游戏或退出游戏。

3. 如果用户选择开始游戏,程序会调用`board`对象的`initBoard`方法,该方法会要求用户输入雷的数量,并在游戏板上随机布置相应数量的雷。

4. 游戏开始后,程序先调用`board`对象的`print_showBoard`方法,显示初始状态下的游戏板给用户。

5. 然后,程序进入一个循环,直到游戏结束。在每次循环中,程序调用`board`对象的`setPoint`方法,要求用户输入要选择的方块的坐标。

6. `setPoint`方法首先检查用户输入的坐标是否合法,然后判断选择的方块是否是雷。如果是雷,游戏结束,显示失败信息。

7. 如果选择的方块不是雷,程序调用`board`对象的`spread_showBoard_noAroundBoom`方法展开周围的安全方块,并更新游戏板的状态。

8. 游戏继续,程序再次调用`board`对象的`print_showBoard`方法,显示更新后的游戏板给用户。

9. 循环继续,直到玩家揭开所有非雷方块或选择到雷方块,游戏结束

10. 游戏结束后,程序显示胜利或失败的信息,并返回到菜单供用户选择继续游戏或退出游戏。

三、程序增强模块化稳定性

        由于考虑到存在用户输入选择的模块,无论在选择是否继续游戏或选点下注都需要涉及到键入值对整形变量的赋值操作,而当我们意外键入非理想的值赋给 int 对象时往往会出现程序崩溃,例如下面给出简单示例:

int a = 0;
for(int i = 0;i<5;i++)
{
	cout << "请输入a的值:" << endl;
	cin >> a;
	cout << "a = " << a << endl;
}

当输入字符时:

       所以通过上面的例子,我们认识到异常处理在程序设计中的必要性,所以应当在遵循高内聚低耦合的前提下增加异常处理模块,以增加程序的健壮性和可使用性。在这里考虑将键入值传给字符串,然后再从字符串中进行读取转化并传给指定的整形变量即可完成赋值,故还应有 StringToInt() 相关函数的实现,同时考虑构建一个异常处理类,以包含所有异常处理函数的静态调用

四、主函数模块实现

以下为主函数模块的基础设定实现:

Board board;
while (true)
{
	menu();
	cout << "请输入您的选择:";
	string choose_s = " ";
	int choose = 0;
	cin >> choose_s;
	choose = ErrorDispose::StringToInt_scope(choose_s, 0, 1);
	if (choose == 1)
	{
		board.initBoard();
		board.print_showBoard();
		board.print_bottomBoard_secure();
		while (!board.is_win())
		{
			if (!board.setPoint()) { return 0; }
			board.print_showBoard();
		}
		cout << "恭喜您,成功获胜!" << endl;
	}
	else    // choose == 0
	{
		cout << "欢迎下次使用,再见!" << endl;
		break;
	}
}

五、异常类的构建(单独将其置于头文件中)

class ErrorDispose
{
public:
	static int StringToInt(const std::string& s)
	{
		int result = 0;
		try
		{
			result = stoi(s);
		}
		catch (const std::out_of_range& e)  // 处理转换后数字过大超出范围
		{
			std::cout << "StringToInt() occurred Out_of_range: " << e.what() << std::endl;
		}
		catch (const std::invalid_argument& e)   // 处理无法转换为整数
		{
			std::cout << "StringToInt() occurred Invalid argument: " << e.what() << std::endl;
		}
		catch (...)
		{
			std::cout << "other Error at StringToInt()" << std::endl;
		}
		return result;
	}
	static int StringToInt_scope(const std::string& s, const int left, const int right)
	{
		int result = 0;
		try
		{
			result = StringToInt(s);
			if (result<left || result>right)
			{
				throw "StringToInt_scope() occurred Turned_num_Scope_error";
			}
		}
		catch (const char* e)
		{
			std::cout << e << std::endl;
		}
		catch (...)
		{
			std::cout << "other Error at StringToInt_scope" << std::endl;
		}
		return result;
	}
};

如对以上异常处理部分存在疑惑,欢迎参考:常见的异常处理icon-default.png?t=N7T8http://t.csdn.cn/abm5z

六、具体扫雷程序功能实现模块

        1)面板类的定义以及类函数的声明

class Board final
{
	friend inline void print_Board(const std::vector<std::vector<char>>& v_2);
public:
	Board() :set_count(0), boom_count(0)
	{
		v_showBoard.resize(ROWS, std::vector<char>(COLS, '*'));
		v_bottomBoard.resize(ROWS, std::vector<char>(COLS, ' '));
	}
	void initBoard();
	void print_showBoard() const;
	void print_bottomBoard_secure() const;     // 受保护访问,需要输入密码
	bool setPoint();
	bool is_win() const;
	void spread_showBoard_noAroundBoom(int a, int b);   // 递归展开选中安全点周围的安全点
private:
	bool is_boom(int x, int y) const;
	int getAroundBoom_num(int x, int y) const;

public:
	std::vector<std::vector<char>> v_showBoard;       // *表示未被选择  #表示踩雷  ' '表示选过未踩雷点
	std::vector<std::vector<char>> v_bottomBoard;     // ' '表示安全点  #表示雷    '@'表示选过的未踩雷点
private:
	size_t set_count;
	size_t boom_count;
};

        2)成员函数的定义和全局函数、友元函数的构建

全局、友元函数:

inline void print_Board(const std::vector<std::vector<char>>& v_2)  // 打印面板
{
	constexpr size_t row = ROW;
	constexpr size_t col = COL;
	std::cout << "  | ";
	for (int j = 1; j <= col; ++j)
	{
		std::cout << j;
		if (j == col) { std::cout << std::endl; }
		else { std::cout << " "; }
	}
	std::cout << "====";
	for(int j = 1; j <= col; ++j)
	{
		std::cout << "=";
		if (j == col) { std::cout << std::endl; }
		else { std::cout << "="; }
	}
	for (int i = 1; i <= row; ++i)
	{
		std::cout << i << " | ";
		for (int j = 1; j <= col; ++j)
		{
			std::cout << v_2.at(i).at(j);
			if (j == col) { std::cout << std::endl; }
			else { std::cout << " "; }
		}
	}
}

#define KEY "xzt"    // 访问底层面板密码
inline bool access_control()     // 加密访问
{
	std::cout << "请输入底层访问密码:";
	std::string key;
	std::cin >> key;
	if (key != KEY) { std::cout << "密码错误,您无权访问!" << std::endl; return false; }
	return true;
}

这里需要提出的点是:测试操作中访问底层密码为"xzt" 对应define KEY 值,在main主函数中board.print_bottomBoard_secure() 函数会调用 access_control() 函数。当然,输入密码错误亦不会影响游戏的游玩体验,没有底层面板显示相当于失去了上帝视角,该设计只在测试时有用,但是实际游玩时建议直接随便键入错误密码,真正沉浸其中!

成员函数:

inline void Board::initBoard()
{
	while (true)    // 出现异常时实现循环输入
	{
		std::cout << "请设定雷的数量:";
		std::string boom_s;
		std::cin >> boom_s;
		const size_t boom = ErrorDispose::StringToInt_scope(boom_s, 1, ROW * COL);
		for (size_t i = 0; i < boom; i++)
		{
			// 注意rand()左闭右开
			const int x = rand() % ROW + 1;
			const int y = rand() % COL + 1;
			if (this->v_bottomBoard.at(x).at(y) == '#') { i--; continue; }   // 重复选点时进行回退操作
			this->v_bottomBoard.at(x).at(y) = '#';
		}
		this->boom_count = boom;
		break;
	}
}

inline void Board::print_showBoard() const
{
	print_Board(this->v_showBoard);
}
inline void Board::print_bottomBoard_secure() const
{
	const bool ret = access_control();
	if (!ret) { return; }
	print_Board(this->v_bottomBoard);
}

inline bool Board::is_boom(int x, int y) const
{
	if (this->v_bottomBoard.at(x).at(y) == '#') { return true; }
	return false;
}

inline int Board::getAroundBoom_num(int x, int y) const
{
	int result = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			if (is_boom(i, j)) { result++; }  // 只要执行该函数说明x,y表示的点为安全点
		}
	}
	return result;
}

inline bool Board::setPoint()
{
	bool ret = true;
	while (ret)
	{
		std::string x_s, y_s;
		std::cout << "请输入选点的横纵坐标(形如:1 2):";
		std::cin >> x_s >> y_s;
		const int x = ErrorDispose::StringToInt_scope(x_s, 1, ROW);
		const int y = ErrorDispose::StringToInt_scope(y_s, 1, COL);
		if (this->v_bottomBoard.at(x).at(y) == '@' || this->v_showBoard.at(x).at(y) == ' ')
		{
			std::cout << "该点已被排除或选择,请重新输入..." << std::endl;
			continue;
		}
		if (this->v_bottomBoard.at(x).at(y) == '#') { std::cout << "您已踩雷,游戏结束" << std::endl; ret = false; break; }
		// 标记已被选择点
		spread_showBoard_noAroundBoom(x, y);
		break;
	}
	this->set_count++;
	return ret;
}

inline bool Board::is_win() const
{
	if ((set_count + boom_count) / COL == ROW) { return true; }
	return false;
}

inline void Board::spread_showBoard_noAroundBoom(int x, int y)
{
	if (x < 1 && x > ROW && y < 1 && y > COL) { std::cout << "spread_showBoard_noAroundBoom()::输入参数不符合要求!" << std::endl;  return; }   // 处理初始不合理输入
	if (v_showBoard.at(x).at(y) == ' ' || is_boom(x, y) || v_bottomBoard.at(x).at(y) == '@') { return; }    // 切记防止判断过的点重复判断,造成死递归

	const int boom_num_around = this->getAroundBoom_num(x, y);    // 0到8
	if(boom_num_around == 0)
	{
		this->v_showBoard.at(x).at(y) = ' ';
		this->v_bottomBoard.at(x).at(y) = '@';    // 利用底层面板标记已经判断过的点,避免死递归用

		if (y > 1)
		{
			spread_showBoard_noAroundBoom(x, y - 1);
		}
		if (y < COL)
		{
			spread_showBoard_noAroundBoom(x, y + 1);
		}
		if (x > 1)
		{
			if (y > 1) { spread_showBoard_noAroundBoom(x - 1, y - 1); }
			spread_showBoard_noAroundBoom(x - 1, y);
			if (y < ROW) { spread_showBoard_noAroundBoom(x - 1, y + 1); }
		}
		if (x < ROW)
		{
			if (y > 1) { spread_showBoard_noAroundBoom(x + 1, y - 1); }
			spread_showBoard_noAroundBoom(x + 1, y);
			if (y < COL) { spread_showBoard_noAroundBoom(x + 1, y + 1); }
		}
	}
	else
	{
		this->v_showBoard.at(x).at(y) = static_cast<char>('0' + boom_num_around);
		this->v_bottomBoard.at(x).at(y) = '@';    // 利用底层面板标记已经判断过的点
	}
}

七、整体代码呈现

<Error_dispose.hpp>

#pragma once
#include <iostream>
#include <string>

class ErrorDispose
{
public:
	static int StringToInt(const std::string& s)
	{
		int result = 0;
		try
		{
			result = stoi(s);
		}
		catch (const std::out_of_range& e)  // 处理转换后数字过大超出范围
		{
			std::cout << "StringToInt() occurred Out_of_range: " << e.what() << std::endl;
		}
		catch (const std::invalid_argument& e)   // 处理无法转换为整数
		{
			std::cout << "StringToInt() occurred Invalid argument: " << e.what() << std::endl;
		}
		catch (...)
		{
			std::cout << "other Error at StringToInt()" << std::endl;
		}
		return result;
	}
	static int StringToInt_scope(const std::string& s, const int left, const int right)
	{
		int result = 0;
		try
		{
			result = StringToInt(s);
			if (result<left || result>right)
			{
				throw "StringToInt_scope() occurred Turned_num_Scope_error";
			}
		}
		catch (const char* e)
		{
			std::cout << e << std::endl;
		}
		catch (...)
		{
			std::cout << "other Error at StringToInt_scope" << std::endl;
		}
		return result;
	}
};

<game.hpp>

#pragma once
#include <iostream>
#include <vector>
#include "Error_dispose.hpp"

enum
{
	ROW = 9,
	COL = ROW,
	ROWS = ROW + 2,
	COLS = COL + 2
};

#define KEY "xzt"
inline bool access_control()     // 加密访问
{
	std::cout << "请输入底层访问密码:";
	std::string key;
	std::cin >> key;
	if (key != KEY) { std::cout << "密码错误,您无权访问!" << std::endl; return false; }
	return true;
}


class Board final
{
	friend inline void print_Board(const std::vector<std::vector<char>>& v_2);
public:
	Board() :set_count(0), boom_count(0)
	{
		v_showBoard.resize(ROWS, std::vector<char>(COLS, '*'));
		v_bottomBoard.resize(ROWS, std::vector<char>(COLS, ' '));
	}
	void initBoard();
	void print_showBoard() const;
	void print_bottomBoard_secure() const;     // 受保护访问,需要输入密码
	bool setPoint();
	bool is_win() const;
	void spread_showBoard_noAroundBoom(int a, int b);   // 递归展开选中安全点周围的安全点
private:
	bool is_boom(int x, int y) const;
	int getAroundBoom_num(int x, int y) const;

public:
	std::vector<std::vector<char>> v_showBoard;       // *表示未被选择  #表示踩雷  ' '表示选过未踩雷点
	std::vector<std::vector<char>> v_bottomBoard;     // ' '表示安全点  #表示雷    '@'表示选过的未踩雷点
private:
	size_t set_count;
	size_t boom_count;
};

inline void Board::initBoard()
{
	while (true)    // 出现异常时实现循环输入
	{
		std::cout << "请设定雷的数量:";
		std::string boom_s;
		std::cin >> boom_s;
		const size_t boom = ErrorDispose::StringToInt_scope(boom_s, 1, ROW * COL);
		for (size_t i = 0; i < boom; i++)
		{
			// 注意rand()左闭右开
			const int x = rand() % ROW + 1;
			const int y = rand() % COL + 1;
			if (this->v_bottomBoard.at(x).at(y) == '#') { i--; continue; }   // 重复选点时进行回退操作
			this->v_bottomBoard.at(x).at(y) = '#';
		}
		this->boom_count = boom;
		break;
	}
}

inline void print_Board(const std::vector<std::vector<char>>& v_2)
{
	constexpr size_t row = ROW;
	constexpr size_t col = COL;
	std::cout << "  | ";
	for (int j = 1; j <= col; ++j)
	{
		std::cout << j;
		if (j == col) { std::cout << std::endl; }
		else { std::cout << " "; }
	}
	std::cout << "====";
	for(int j = 1; j <= col; ++j)
	{
		std::cout << "=";
		if (j == col) { std::cout << std::endl; }
		else { std::cout << "="; }
	}
	for (int i = 1; i <= row; ++i)
	{
		std::cout << i << " | ";
		for (int j = 1; j <= col; ++j)
		{
			std::cout << v_2.at(i).at(j);
			if (j == col) { std::cout << std::endl; }
			else { std::cout << " "; }
		}
	}
}

inline void Board::print_showBoard() const
{
	print_Board(this->v_showBoard);
}
inline void Board::print_bottomBoard_secure() const
{
	const bool ret = access_control();
	if (!ret) { return; }
	print_Board(this->v_bottomBoard);
}

inline bool Board::is_boom(int x, int y) const
{
	if (this->v_bottomBoard.at(x).at(y) == '#') { return true; }
	return false;
}

inline int Board::getAroundBoom_num(int x, int y) const
{
	int result = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			if (is_boom(i, j)) { result++; }  // 只要执行该函数说明x,y表示的点为安全点
		}
	}
	return result;
}

inline bool Board::setPoint()
{
	bool ret = true;
	while (ret)
	{
		std::string x_s, y_s;
		std::cout << "请输入选点的横纵坐标(形如:1 2):";
		std::cin >> x_s >> y_s;
		const int x = ErrorDispose::StringToInt_scope(x_s, 1, ROW);
		const int y = ErrorDispose::StringToInt_scope(y_s, 1, COL);
		if (this->v_bottomBoard.at(x).at(y) == '@' || this->v_showBoard.at(x).at(y) == ' ')
		{
			std::cout << "该点已被排除或选择,请重新输入..." << std::endl;
			continue;
		}
		if (this->v_bottomBoard.at(x).at(y) == '#') { std::cout << "您已踩雷,游戏结束" << std::endl; ret = false; break; }
		// 标记已被选择点
		spread_showBoard_noAroundBoom(x, y);
		break;
	}
	this->set_count++;
	return ret;
}

inline bool Board::is_win() const
{
	if ((set_count + boom_count) / COL == ROW) { return true; }
	return false;
}

inline void Board::spread_showBoard_noAroundBoom(int x, int y)
{
	if (x < 1 && x > ROW && y < 1 && y > COL) { std::cout << "spread_showBoard_noAroundBoom()::输入参数不符合要求!" << std::endl;  return; }   // 处理初始不合理输入
	if (v_showBoard.at(x).at(y) == ' ' || is_boom(x, y) || v_bottomBoard.at(x).at(y) == '@') { return; }    // 切记防止判断过的点重复判断,造成死递归

	const int boom_num_around = this->getAroundBoom_num(x, y);    // 0到8
	if(boom_num_around == 0)
	{
		this->v_showBoard.at(x).at(y) = ' ';
		this->v_bottomBoard.at(x).at(y) = '@';    // 利用底层面板标记已经判断过的点,避免死递归用

		if (y > 1)
		{
			spread_showBoard_noAroundBoom(x, y - 1);
		}
		if (y < COL)
		{
			spread_showBoard_noAroundBoom(x, y + 1);
		}
		if (x > 1)
		{
			if (y > 1) { spread_showBoard_noAroundBoom(x - 1, y - 1); }
			spread_showBoard_noAroundBoom(x - 1, y);
			if (y < ROW) { spread_showBoard_noAroundBoom(x - 1, y + 1); }
		}
		if (x < ROW)
		{
			if (y > 1) { spread_showBoard_noAroundBoom(x + 1, y - 1); }
			spread_showBoard_noAroundBoom(x + 1, y);
			if (y < COL) { spread_showBoard_noAroundBoom(x + 1, y + 1); }
		}
	}
	else
	{
		this->v_showBoard.at(x).at(y) = static_cast<char>('0' + boom_num_around);
		this->v_bottomBoard.at(x).at(y) = '@';    // 利用底层面板标记已经判断过的点
	}
}

<源.cpp> 

#include "game.hpp"
#include <windows.h>

using namespace std;

void menu()
{
	cout << "****************************" << endl;
	cout << "********** 请选择  *********" << endl;
	cout << "********** 1. play *********" << endl;
	cout << "********** 0. exit *********" << endl;
	cout << "****************************" << endl;
	cout << "****************************" << endl;
}

int main()
{
	const wstring title = L"Mine_sweeper Program Powered By XZT";
	SetConsoleTitle(title.c_str());
	srand(static_cast<unsigned int>(time(nullptr)));
	Board board;
	while (true)
	{
		menu();
		cout << "请输入您的选择:";
		string choose_s = " ";
		int choose = 0;
		cin >> choose_s;
		choose = ErrorDispose::StringToInt_scope(choose_s, 0, 1);
		if (choose == 1)
		{
			board.initBoard();
			board.print_showBoard();
			board.print_bottomBoard_secure();
			while (!board.is_win())
			{
				if (!board.setPoint()) { return 0; }
				board.print_showBoard();
			}
			cout << "恭喜您,成功获胜!" << endl;
		}
		else    // choose == 0
		{
			cout << "欢迎下次使用,再见!" << endl;
			break;
		}
	}
	return 0;
}

总结

        本文介绍了一个功能齐全、高效的带异常处理的扫雷游戏程序。通过使用C++语言编写,结合各种技术和特点,使得游戏体验更流畅,并避免了可能导致死循环的异常情况。程序实现了扫雷游戏的基本规则设定,包括游戏板的创建、雷的布置、方块状态的管理、游戏操作的处理、方块的揭开以及游戏结束条件的判断等。用户界面简洁明了,提供了良好的用户体验。此外,为提高程序的稳定性和可扩展性,文章还介绍了增强模块化稳定性的方法。通过异常处理模块,有效解决了可能出现的输入异常和程序控制异常,提高了程序的健壮性和可使用性。

        通过阅读本文,读者将了解如何使用C++语言设计一个高效、稳定的扫雷游戏程序,并掌握异常处理的技巧和应用场景。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

螺蛳粉只吃炸蛋的走风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值