吴昊品游戏核心算法(新年特别篇)—— 吴昊教你玩扫雷(模拟)(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:

 

 1  /*
 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, - 100111};
20   int dc[ 8] = {- 101, - 11, - 101};
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  }

 

你可能感兴趣的:(poj)