啊哈算法之炸弹人
简述
本算法摘选自啊哈磊所著的《啊哈!算法》第三章第二节的题目——枚举法破解炸弹人游戏。文中代码使用C语言编写,博主通过阅读和理解,重新由Java代码实现了一遍。
游戏简述
通过放置炸弹的方法来消灭敌人,炸弹只能放置在空地上,炸弹可以向上下左右四个方向炸开,碰到敌人可以消灭,但是碰到墙则停止这个炸弹在这个方向上的威力。
(图片来源于啊哈算法书中截图)
现在有个特殊的关卡,只有一枚炸弹(炸弹威力大杀伤距离足够强威力足够大),请问这个炸弹放置在地图上的哪个空位上,能够消灭最多数量的敌人。
(图片来源于啊哈算法书中截图)
程序建模
先将地图模型化,把墙用符号“#”表示,敌人用大写字母G表示,空地用英文点号“.”表示,整张地图可以抽象成一个二维数组。
至于炸弹放置在某个点可以消灭的敌人数最多,则需要一个一个点来尝试。炸弹爆炸的方向是沿着上下左右的,因此我们可以对每个点的上下左右四个方向去遍历能够消灭的敌人数量。最终输出可以消灭的最多敌人数以及放置该枚炸弹的点。
代码实现
首先声明,x和y是点所在的那一行和列交叉的点,不要和xy坐标扯上关系哦。
如何分别统计上下左右四个方向上可以消灭的敌人数量呢,其实只需要搞清楚一个方向,其他方向都是一样的道理,这里以向下统计为例,向下就是y不变,x每次加一,直到遇到墙为止。
1 while(map[x][y] != '#') { 2 3 // 先往这个方向的下一个目标看 4 x++; 5 6 // 如果下个位置目标是敌人则消灭,计数 7 if(map[x][y] == 'G') { 8 sum++; 9 } 10 }
整个算法代码如下:
1 public static void main(String[] args) { 2 // 初始化一张游戏地图,将地图抽象成一个二维数组,其中墙为“#”,敌人为“G”,空地为“.” 3 // char[][] map = new char[10][10]; 4 char[][] map = { 5 {'#', '#', '#', '#', '#', '#', '#', '#', '#', '#'}, 6 {'#', 'G', 'G', '.', 'G', 'G', 'G', '#', '.', '#'}, 7 {'#', '#', '#', 'G', '#', '.', 'G', 'G', 'G', '#'}, 8 {'#', '#', '.', '.', '#', 'G', '#', '#', '#', '#'}, 9 {'#', '.', '#', 'G', '#', 'G', 'G', 'G', '.', '#'}, 10 {'#', '.', '#', 'G', '.', '.', '#', '#', 'G', '#'}, 11 {'#', '#', '.', 'G', 'G', 'G', 'G', '.', 'G', '#'}, 12 {'#', 'G', '.', '#', '.', '#', '.', '.', '#', '#'}, 13 {'#', '#', '#', '.', '#', '#', 'G', 'G', '.', '#'}, 14 {'#', '#', '#', '#', '#', '#', '#', '#', '#', '#'} 15 }; 16 17 // 假定矩形地图长宽都是10个单位的物体 18 int len = 10; 19 20 // 能够消灭的最多的敌人数量以及炸弹放置的位置 21 int max = 0, m = 0, n = 0; 22 23 // 用两重循环枚举(遍历)地图中的每一个点 24 for(int i = 0; i < len; i++) { 25 for(int j = 0; j < len; j++) { 26 27 // 判断当前节点是否为“.”表示的空地,只有空地才能放置炸弹 28 if(map[i][j] == '.') { 29 30 // 尝试在此放置炸弹,统计上下左右可以消灭的敌人数量 31 int sum = 0; 32 33 // 从当前位置开始向上下左右四个方向消灭敌人 34 int x = i; 35 int y = j; 36 37 // 从原位开始,向上消灭敌人,如果遇到墙“#”则停止 38 while(map[x][y] != '#') { 39 40 // 先往这个方向的下一个目标看 41 x--; 42 43 // 如果下个位置目标是敌人则消灭,计数 44 if(map[x][y] == 'G') { 45 sum++; 46 } 47 } 48 49 // 回到原位,向下消灭敌人,如果遇到墙“#”则停止 50 x = i; 51 y = j; 52 while(map[x][y] != '#') { 53 54 // 先往这个方向的下一个目标看 55 x++; 56 57 // 如果下个位置目标是敌人则消灭,计数 58 if(map[x][y] == 'G') { 59 sum++; 60 } 61 } 62 63 // 回到原位,向左消灭敌人,如果遇到墙“#”则停止 64 x = i; 65 y = j; 66 while(map[x][y] != '#') { 67 68 // 先往这个方向的下一个目标看 69 y--; 70 71 // 如果下个位置目标是敌人则消灭,计数 72 if(map[x][y] == 'G') { 73 sum++; 74 } 75 } 76 77 // 回到原位,向右消灭敌人,如果遇到墙“#”则停止 78 x = i; 79 y = j; 80 while(map[x][y] != '#') { 81 82 // 先往这个方向的下一个目标看 83 y++; 84 85 // 如果下个位置目标是敌人则消灭,计数 86 if(map[x][y] == 'G') { 87 sum++; 88 } 89 } 90 91 // 如果当前消灭的敌人数比之前消灭的敌人数多,刷新记录 92 if(sum > max) { 93 max = sum; 94 m = i; 95 n = j; 96 } 97 } 98 } 99 } 100 101 // 枚举(遍历)完所有的情况之后,打印得出炸弹放置位置和能够消灭的最多敌人的数量 102 System.out.println(String.format("将炸弹放置在(%d, %d),最多可以消灭%d个敌人。", m, n, max)); 103 }
学习总结
炸弹人的游戏解法思路的关键点是数学建模,将整张地图抽象化为一个二维数组,然后从数组中去枚举(遍历)得到最终的答案,数学建模的能力对于经常开发业务的同学来说算是弱项了,借此机会学习和积累一下这种解决问题的方式。
参考资料
1、《啊哈!算法》/ 啊哈磊著. 人民邮电出版社
原创作者:Captain&D
Captain&D所发布的博文均为原创,概不任意转载,如有参考必定给出原文链接。
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。