分析:
PS:大模拟题越来越像阅读理解题,明明可以很简短的描述规则,非要描述几页纸,生怕你很容易就读懂题目了。
首先用通俗易懂的语言简化下题目:给你一个n * m的棋盘,初始状态每个格子不是.就是*,*表示有地雷,.表示没有地雷。
在扫雷过程中有三种状态:初始状态(未插旗子),插了旗子,已探明。前面两种状态都属于未探明,什么叫已探明呢?就是点开了格子。然后有四种操作:Quit-退出游戏;Flag-插入和取消旗帜;Sweep-扫雷;DSweep-深度扫雷。
由于游戏规则较为复杂,下面先介绍程序的设计思路,逐步解释详细的规则。
思路:
第一步,确定存储。一个char类型的二维数组用于存储棋盘的初始状态,这个必不可少。至于三种状态,用int类型的一个二维数组st表示即可,对于某个方格,0-未插旗子,1-插了旗子,2-已探明。
第二步,怎么读入。大模拟题的数据这么大自然用scanf读入最为合适,而且注意行列最好都要从下标为1的位置开始读入(方便处理)。读完初始数据后读操作,如果操作是Quit游戏就结束了,否则要继续读入待操作位置的横纵坐标。
第三步,基本的处理。除了要进行扫雷操作的处理复杂点,其他就是简单的分支判断,看下面代码即可明确。
第四步,扫雷。
首先明确触发扫雷操作的条件,对于普通扫雷操作Sweep,只有待操作位置的状态是0(没插旗子也没探明)时,才对该位置进行扫雷,一旦点开了有地雷的格子,自然就Boom了。另外,只要点开了一个没有地雷的格子,该格子就会显示相邻八连通区域内地雷的个数,同时该格子状态也变为已探明了。何为八连通区域,题目默认我们都知道是啥,但是好像没那么显而易见,一般搜索无非上下左右四个方向,对于一个3*3的区域,中间格子被八个格子包围,这八个格子就是中间格子的八连通区域,也就意味着我们在扫雷过程中点开一个雷要统计一下八连通区域内一共有多少个地雷。如果sweep了一下某个格子发现该格子显示了0(四周没有地雷),就会进行连锁扫雷(这个一会再说)。相对于Sweep操作,DSweep操作的触发条件便稍微严苛了,只有待操作的格子已被探明,也就是已经点开了,而且格子上面显示的数字必须和你在附近八连通区域已经放置旗帜的个数相等才可能触发。也就是说,对于Sweep x y操作,原则上只是想点开坐标为(x,y)的格子,而DSweep x y操作则是想对(x,y)附近的八连通区域进行扫雷。具体规则是,遍历八连通区域,遇见插旗子的和已探明的就不扫,遇见没插旗子的就点开,点开是炸弹就Boom,否则会显示附近的地雷数目,数目是0也会进行连锁扫雷。
上面已经介绍了扫雷操作的细节,除了连锁扫雷操作。对于连锁扫雷操作,触发条件为点开一个格子显示的数字是0,也就意味着附近没有地雷,于是开始遍历八连通区域,一个个点开,如果点开显示的数字还是0,就重复连锁扫雷的步骤。需要注意的是,如果扫到了有旗帜的方格,会无视旗帜去点开,否则不会有地雷,只有遇见已探明的格子才会停下搜索的脚步。这个步骤本身是递归的,却不能使用DFS完成,如果这样写连锁扫雷的操作:
void sweepagain(int x, int y, vector<Info> &r) {
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || ny < 1 || nx > n || ny > m || st[nx][ny] == 2) continue;
int num = countflag(nx, ny, 1);
a[nx][ny] = num + '0';
r.push_back({ nx,ny,num });
st[nx][ny] = 2;//已探明
if (a[nx][ny] == '0') sweepagain(nx, ny,r);
}
}
代码会简洁一点,但是由于数据量较大在测试后面的评测点时就会爆栈,于是可以考虑改成BFS实现,具体实现见完整代码。
说到这扫雷的主要过程就结束了,但是出题人不会这样放过我们的,还是需要我们统计扫雷结果的,每次扫雷点开了哪几个格子,格子坐标和点开后显示的数字分别是多少。而且最后输出前还要按照坐标的字典序进行有序输出,考虑在扫雷函数里添加一个向量保存结果,在输出前先排下序即可解决。
最后一个问题,如何判断结束,只需在一开始统计下没有地雷的格子有多少个,然后每次操作都减去该次操作探明的格子数目,在Sweep和DSweep操作后判断下剩余未探明的格子数目是否为0,为0就结束游戏。
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <string>
using namespace std;
const int maxn = 1005;
char a[maxn][maxn];
int st[maxn][maxn];//0-未插旗子,1-插了旗帜,2-已探明
int dx[] = {-1,0,1,-1,1,-1,0,1};//方向向量
int dy[] = {1,1,1,0,0,-1,-1,-1};
int n, m,cnt = 0,mcnt = 0;
struct Info {//扫雷信息
int x;
int y;
int num;
};
int countflag(int x, int y,int type) {//type:0-统计八连通区域旗帜数;1-统计八连通区域地雷数
int res = 0;
if (!type) {
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || ny < 1 || nx > n || ny > m) continue;
if (st[nx][ny] == 1) res++;
}
}
else
{
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || ny < 1 || nx > n || ny > m) continue;
if (a[nx][ny] == '*') res++;
}
}
return res;
}
void sweepagain(int x, int y, vector<Info> &r) {//连锁扫雷BFS
queue<pair<int, int> > q;
q.push({ x,y });
while (q.size()) {
int x = q.front().first, y = q.front().second;
q.pop();
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || ny < 1 || nx > n || ny > m || st[nx][ny] == 2) continue;
int num = countflag(nx, ny, 1);
a[nx][ny] = num + '0';
r.push_back({ nx,ny,num });
st[nx][ny] = 2;//已探明
if (a[nx][ny] == '0') q.push({ nx,ny });
}
}
}
void sweepmine(int x, int y,vector<Info> &r,int type) {//type:0-sweep,1-dsweep
if (type == 0) {
if (a[x][y] == '*') {
printf("boom\n");
printf("game over\n");
printf("total step %d\n", cnt);
exit(0);
}
int num = countflag(x, y, 1);
a[x][y] = num + '0';
r.push_back({ x,y,num });
st[x][y] = 2;//已探明
if (a[x][y] == '0') sweepagain(x, y, r);
}
else
{
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || ny < 1 || nx > n || ny > m || st[nx][ny] != 0) continue;
if (a[nx][ny] == '*') {
printf("boom\n");
printf("game over\n");
printf("total step %d\n", cnt);
exit(0);
}
int num = countflag(nx, ny, 1);
a[nx][ny] = num + '0';
r.push_back({nx,ny,num});
st[nx][ny] = 2;//已探明
if (a[nx][ny] == '0') sweepagain(nx, ny, r);
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%s", a[i] + 1);//*-地雷,.-安全
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] == '.') mcnt++;//没有地雷的格子数目
}
}
char op[10];
while (scanf("%s", op)) {
string o(op);
if (o == "Quit") {//放弃游戏
printf("give up\n");
printf("total step %d\n", cnt);
exit(0);
}
else {
cnt++;
int x, y;
scanf("%d%d", &x, &y);
vector<Info> result;
if (o == "Flag") {
if (st[x][y] == 2) printf("swept\n");
else{
if (!st[x][y]) printf("success\n");
else printf("cancelled\n");
st[x][y] ^= 1;
}
}
else if (o == "Sweep") {
if (st[x][y] == 2) printf("swept\n");
else if (st[x][y] == 1) printf("flagged\n");
else {
sweepmine(x, y, result,0);
if (result.size()){
printf("%d cell(s) detected\n", result.size());
sort(result.begin(), result.end(), [=](Info i1, Info i2) {if (i1.x != i2.x) return i1.x < i2.x; return i1.y < i2.y; });
for_each(result.begin(), result.end(), [=](Info s) {printf("%d %d %d\n", s.x, s.y, s.num); });
mcnt -= result.size();
}
}
if (!mcnt) {
printf("finish\n");
printf("total step %d\n", cnt);
exit(0);
}
}
else if (o == "DSweep") {
if (st[x][y] != 2) printf("not swept\n");
else if(a[x][y] == '0' || countflag(x,y,0) != a[x][y] - '0') printf("failed\n");
else{
sweepmine(x, y, result,1);
if (!result.size()) printf("no cell detected\n");
else {
printf("%d cell(s) detected\n",result.size());
sort(result.begin(), result.end(), [=](Info i1, Info i2) {if (i1.x != i2.x) return i1.x < i2.x; return i1.y < i2.y; });
for_each(result.begin(), result.end(), [=](Info s) {printf("%d %d %d\n", s.x, s.y, s.num); });
mcnt -= result.size();
}
}
if (!mcnt) {
printf("finish\n");
printf("total step %d\n", cnt);
exit(0);
}
}
}
}
return 0;
}