Qt 扫雷游戏设计(一)

5 篇文章 0 订阅

这次打算以扫雷游戏,做一个Qt界面设计的总结过程

这第一篇以介绍扫雷的算法,实现一个控制台版本为起点

先来查看扫雷游戏的程序设计,玩法就不介绍了:

1、首先便是要随机生成地雷,可以利用rand生成

这里以一个简单的9*9的一维数组模拟2维数组作为存储结构,以简单的几个数字来标记每个方块的状态
-1表示有雷
0-8表示周围八个方块的雷数情况
10表示右键点击标记的真雷
11表示右键点击标记的假雷,也就是猜错了,其实这不是雷
12表示左键点击的无雷区

这就是用来表示方块数据的数据结构

2、每局开始之前,先遍历一边所有的方块,将雷数缓存下来,这样,之后再使用时可以减少计算,当地图生成之后,这个就可以计算了

3、关于绘制图像,即以左键点击和右键点击来决定绘制的图片是怎样的,不过作为控制台程序,这里就是以下方式显示

"*"表示未被点击状态

"F" 表示右键标记状态,即假定这是个雷

" " 空格表示左键点击,且此处无雷

4、关于左键点击之后,即应该遍历判断点击区域周围的八个方向,若该方向被点击或有雷则停止,若遍历的方块的雷数仍然为0,则继续递归判断,直到不满足条件,或超出边界。


这里关于扫雷的大体设计即介绍完毕,接下来以代码演示看一下:

1、创建雷区

// 初始化8个雷
int map[9 * 9] = {0};
int minesTotal = 8;
for (int i = 0; i < minesTotal; ++i)
{
	map[i] = -1;
}

// 此函数由STL算法提供,可用于将数组乱序
random_shuffle(map, map + 9 * 9);
通过上述代码,即构建好雷区,这里利用random_shuffle来生成随机地图

2、迭代整个地图,来缓存每个方块存储的雷数

// 预先计算,缓存雷数
for (int i = 0; i < 9 * 9; ++i)
{
	int row = i / 9;
	int col = i % 9;
	countMine(row, col, map);
}

现在查k难countMine函数,这里先看一个最直接简单的方式

int toIndex(int row, int col)
{
	return row * 9 + col;
}
// 这里用最直接的方式,8个if,每个都计算一个邻近方块的有雷情况
void countMine(int row, int col, int *map)
{
	if (map[toIndex(row, col)] == -1)
		return;

	int nums = 0;
	if (row-1 >= 0 && col-1 >=0 && map[toIndex(row-1, col-1)] == -1)
		++nums;
	if (row-1 >= 0 && map[toIndex(row-1, col)] == -1)
		++nums;
	if (row-1 >= 0 && col+1 <=8 && map[toIndex(row-1, col+1)] == -1)
		++nums;
	if (col-1 >=0 && map[toIndex(row, col-1)] == -1)
		++nums;
	if (col+1 <=8 && map[toIndex(row, col+1)] == -1)
		++nums;
	if (row+1 <= 8 && col-1 >=0 && map[toIndex(row+1, col-1)] == -1)
		++nums;
	if (row+1 <= 8 && map[toIndex(row+1, col)] == -1)
		++nums;
	if (row+1 <= 8 && col+1 <=8 && map[toIndex(row+1, col+1)] == -1)
		++nums;
	map[toIndex(row, col)] = nums;
}

哈哈,这种方式,不动脑子哦,下面用循环改写下

bool isMine(int row, int col, int *map)
{
	int index = toIndex(row, col);
	return (map[index] == -1)
}

bool isValidIndex(int row, int col)
{
	if (0 <= row && row < 9 && 0 <= col && col < 9)
		return true;
	return false;
}

void countMine(int row, int col, int *map)
{
	if (isMine(row, col, map))
		return;
	
	int minesCount = 0;
	for (int aroundRow = row - 1; aroundRow <= row + 1; ++aroundRow)
	{
		for (int aroundCol = col - 1; aroundCol <= col + 1; ++aroundCol)
		{
			// 判断邻近周围8个方块的是否有雷,来统计本方块的雷数
			bool bvalidIndex = isValidIndex(aroundRow, aroundCol);
			bool bSameBlock = (row == aroundRow && col == aroundCol);
			bool bMine = isMine(aroundRow, aroundCol);
			if (bvalidIndex && !bSameBlock && isMine)
				++minesCount;
		}
	}
	map[toIndex(row, col)] = minesCount;
}

利用循环,改写上述重复的if判断

3、处理完成之后,应该游戏开始,这里即以一个死循环开始,满足胜利或失败条件即退出

while (true)
{
	// 输出欢迎语
	cout << "\ninput the cordinate (like 5 5, & f for flag, e for empty)\n";
	// 获取输入
	int row, col;
	char ch;
	cin >> row >> col >> ch;
	
	// 检查此次的输入,应该达到的显示效果
	if (checkMap(row - 1, col - 1, ch, map))
	{
		// 这里不小心点到雷即退出游戏,这里满足失败条件
		cout << "\n                You Lose !!!!!!!!!!!!!!!!\n\n";
		break;
	}
		
	if (minesRemain == 0)
	{
		// 这里满足胜利条件
		cout << "\n                You Win !!!!!!!!!!!!!!!!!\n\n";
		break;
	}
		
	// 显示打印地图,此轮结束,继续下轮
	showMap(map);
}
可以看到,整个游戏的逻辑判断,即位于checkMap中

checkMap很简单,只是依据输入,进行判断,逻辑没有复杂的,唯一就是在点击无雷区时,需要判断是否需要展开周围雷区

// 返回真表示点到雷,挂掉
// 假则表示这次点击正确
bool checkMap(int row, int col, char ch, int *map)
{
	bool status = false;
	if (isMine(row, col, map))
	{
		if (ch == 'f')
		{
			map[toIndex(row, col)] = 10;
			--minesRemain;
		}
		else if (ch == 'e')
		{
			status = true;
		}
	}
	else
	{
		if (ch == 'f')
		{
			map[toIndex(row, col)] = 11;
		}
		else if (ch == 'e')
		{
			map[toIndex(row, col)] = 12;
			checkAround(row, col, map);
		}
	}
	return status;
}

checkAround的实现,就如所countMines中一样,这里直接利用循环,遍历邻近8个方块,然后继续判断是否需要进行递归

整个函数只有在该方块的雷数为0的情况下才需要进行递归判断,若雷数不为0,则不需要再去判断周围雷区情况。

void checkAround(int row, int col, int *map)
{
	if (map[toIndex(row, col)] == 0)
	{
		for (int aroundRow = row - 1; aroundRow <= row + 1; ++aroundRow)
		{
			for (int aroundCol = col - 1; aroundCol <= col + 1; ++aroundCol)
			{
				bool bvalidIndex = isValidIndex(aroundRow, aroundCol);
				bool bSameBlock = (row == aroundRow && col == aroundCol);
				if (bvalidIndex && !bSameBlock)
				{
					if (map[toIndex(aroundRow, aroundCol)] == 12)
						continue;
					
					// 设置此处被点击,以后遍历直接跳过
					map[toIndex(aroundRow, aroundCol)]= 12;
					checkAround(aroundRow, aroundCol, map);
				}
			}
		}
	}
}

至此,游戏主体和逻辑已经介绍完毕,之后的打印函数,就不在显示了。


下面给出,控制台版本的代码,这部分代码已经把上述封装成类,有兴趣的可以前去下载。

http://download.csdn.net/detail/satanzw/5825767

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值