吴昊品游戏核心算法(新年特别篇)—— 吴昊教你玩扫雷(模拟)(POJ 2612)
扫雷历史:
1985年,“方块”被改写成了游戏"Relentless Logic"[2](简称为“Rlogic”)。在“Rlogic”里,玩家的任务是作为美国海军陆战队队员,为指挥中心探出一条没有地雷的安全路线,如果路全被地雷堵死就算输。两年后,汤姆·安德森(Tom Anderson)在“Rlogic”的基础上又编写出了游戏“XMines”[3](地雷),由此奠定了现代扫雷游戏的雏形。
在此基础上,1989年开始受雇于微软公司的两位工程师罗伯特·杜尔(Robert Donner)和卡特·约翰逊(Curt Johnson)开发出了扫雷游戏。微软于1990年10月收购了扫雷的版权,并随纸牌游戏(Solitaire)一同加载到1992年发布的Windows 3.1系统上,从此扫雷才正式在全世界推广开来[4]。虽然历经多次外观变化,Windows自带的扫雷——winmine 一直是最流行的扫雷版本。在Windows 8中,扫雷游戏依然存在,但被重命名为Microsoft Minesweeper[5] ,在Windows Store上提供下载。
虽然winmine非常流行,但其在防作弊方面比较薄弱,功能也有限制。2003年,第一个具有录像功能的扫雷克隆版本出现(后来命名为 Minesweeper X)[6]。2004年初,功能更强大的 Minesweeper Clone 也释放出首个测试版本。此后,扫雷爱好者多使用有限的几种扫雷克隆版本交流记录,参与排行。
如图所示,此为一个标准的扫雷游戏(初级版本),大家应该都会玩吧!规则还是提一下吧,数字表示以数字标识的那一格的周围八个格子有多少雷,所以最大的数字可以为8的应该。
不同版本的扫雷
Windows XP中, 地雷是随意配置的,玩家初次点击的方块若刚好为地雷的方块,那么这颗地雷会消失而转移到左上角的方块。如果左上角原来就有地雷的话,就会换到其邻近的方 块,次序为左→右、上→下。配置改变后,游戏会以尚未初次点击的状态下继续进行,这样的动作是确保玩家不会在第一次点击时就失败。
Windows Vista的初次点击机制是相同的,然而,其与Windows XP不同的是初次点击方块四周的方块也都是安全的,此举是为了保证初次点击方块是空白的。
扫雷与人生哲理
1.刚开始的时候,不知道怎么玩,连说明书也不看,一阵乱按,终于知道怎么把地雷用红旗标注,以为我会玩这个游戏了——人刚来这个世界的时候,乱打乱撞,知道这个世界的一点点规则之后就以为自己掌握了这个世界了。
2.继续玩,游戏难度有高中低三个等级,时限都是999秒,最低等级的我一直打不过去,最后我就怀疑是游戏有问题——小的时候,不小心在地上摔一跤,想当然认为是因为地不平我才摔跤的。
3.去洗手间,坐公交,等餐等闲暇的时间,经常玩这个游戏,技术有些长进,终于有天临睡前赢了一把——积累点滴,从量变到了质变,终于赢得一场胜利。
4.开始有点体会了,开局的时候,没有任何信息能判断哪个点是地雷,只能是一阵乱点,运气好一点的,很快就能点到一片宽阔的无雷区,运气不好的频频踩上地雷——有些东西是命里注定的,就像不能选择出生的环境一样。但是如果你不去试的话,永远不知道哪儿是无雷区。
5.到中场的时候,可能回遇上死局,无法判断哪个点是雷,哪个点不是雷,只有凭运气去赌了。根据当前的形势,赢的几率是可以判断的——,选择才上地雷纪律最小的方法,N害相权取其轻者。
6.有一段时间,我的最好记录是246秒,我想我已经无法突破这个记录了——自我感觉良好,觉得自己已经做到最好了。
7.一次在洗手间,无意间玩出了一把183秒的成绩,又一次在洗手间玩出了126秒的成绩——没有最好,只有更好,将一些琐碎的时间做一些琐碎的事,可能会有更大的收获,惊喜总是不期而至。
8.玩出这么好的成绩我是不是很厉害?NO,不要忘了,还有制定规则的人——写扫雷游戏的工程师,我的一切行为必须在他制定的规则下进行——人在江湖,身不由己,必须遵循一定的规则。
扫雷与诗歌
死了也要玩。
是我对扫雷的情结。
最近越发喜欢一边扫雷一边想东西。
手上不停,脑子不止,想的还都是哲学问题。
比如我们从哪里来,到哪里去。
快乐悲伤平和愤怒的人生。
扫雷其实能告诉我们很多东西。
比如人生就要一步一步来。
有时候开局很棒,接下来也很顺利。
即使开局不好,也可以重头再来。
无论好的还是坏的,都不一定。
走下去,要动脑筋,
有时候会错,错了就记住规律,下次再来过,
有时候要暂时把问题放到一边,也许下一步就峰回路转,
有时候还需要赌一把,有时候赢,有时候失败。
从大学里开始玩这个游戏,到现在,最高记录已经突破100秒。
虽然“无它,唯手熟尔”,我依然热爱这个游戏。
它不是简单重复。
而是在不停挑战自己。
扫雷与世界记录(选自国际扫雷网)
最后我们来看看扫雷与算法(这里给出模拟,AI以后再聊,Source:POJ 2612)
输入首先为棋盘的大小,然后是一个8*8的棋盘,盘面上有一些点和一些星星,星星代表雷而点表示无雷区。在后面的8*8的棋盘中,x代表已经被点开(或者是由于某些格子被点开而自动开启的空间),在输出中,我们需要用数字描述某些点是否有雷。
Solve:
2 Highlights:
3 (1)注意这里并没有对二维数组map[Max][Max]进行初始化,这是一个比较危险的行为,对于一些NB的编译器可能会自动初始化为0,有些可能报错
4 (2)定义方向,朝着八个方向搜索,问题是,我们并不需要BFS或者DFS,因为这里的搜索不具有连续性,所以,在遇到雷的时候,周边标记就可以了
5 (3)在最后的输出中,需要分已经触到雷和没有触到雷两种情况讨论,这里,由于算法不具备动态性,所以,可能存在一个BUG,就是最后输出的时候
6 会碰到两个以上的触雷情况,在真正的游戏中就不可能存在"死了两次"这种情况了
7 */
8
9 #include<iostream>
10 using namespace std;
11
12 //定义棋盘的大小,尽量开大一点
13 const int Max = 12;
14
15 int map[Max][Max];
16 //用布尔变量定义触摸点和有雷点
17 bool touch[Max][Max], mine[Max][Max];
18 //在八个方向上标识,便于搜索
19 int dr[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
20 int dc[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
21
22 int main()
23 {
24 int n, i, j, k;
25 char c;
26 bool flag = false;
27 //输入棋盘的尺寸
28 cin >> n;
29 for(i = 1; i <= n; i ++)
30 for(j = 1; j <= n; j ++)
31 {
32 cin >> c;
33 if(c == '*')
34 {
35 mine[i][j] = true;
36 for(k = 0; k < 8; k ++)
37 {
38 int r = i + dr[k];
39 int c = j + dc[k];
40 //遇到雷的时候,其周围的八个方向对应的格子都需要标注增加的雷数
41 map[r][c]++;
42 }
43 }
44 }
45 for(i = 1; i <= n; i ++)
46 for(j = 1; j <= n; j ++)
47 {
48 cin >> c;
49 if(c == 'x')
50 {
51 touch[i][j] = true;
52 if(mine[i][j]) flag = true;
53 }
54 }
55 //触雷情况的讨论
56 if(flag)
57 {
58 for(i = 1; i <= n; i ++)
59 {
60 for(j = 1; j <= n; j ++)
61 {
62 if(mine[i][j])
63 cout << '*';
64 else if(touch[i][j])
65 cout << map[i][j];
66 else
67 cout << '.';
68 }
69 //每输入一行之后,是需要换行的
70 cout << endl;
71 }
72 }
73 //没有触雷的情况的讨论
74 else
75 {
76 for(i = 1; i <= n; i ++)
77 {
78 for(j = 1; j <= n; j ++)
79 {
80 if(touch[i][j])
81 cout << map[i][j];
82 else
83 cout << '.';
84 }
85 cout << endl;
86 }
87 }
88 return 0;
89 }