这次打算以扫雷游戏,做一个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