连连看曾经是一款非常受欢迎的游戏,同时它也是一款比较古老的游戏。看到这里你千万不要认为本篇文章打算讨论《连连看》的历史以及它取得的丰功伟绩。恰恰相反,在这篇文章中我们打算讨论该游戏背后的实现思想,包括它定义的游戏规则,以及游戏的实现算法。作为应用,我们还将利用Java代码实现一个通用的《连连看》算法,并使用Java Swing框架创建一个演示实例。
1《连连看》的游戏规则是如何定义的?
连连看的游戏界面和游戏规则都非常简单。游戏界面可以简单看作一个具有M×N个单元格的棋盘,每个单元格内部显示着各种图片,游戏的最终目的是消除所有图片。但是在消除的过程中,我们需要遵守以下规则:
- 只有内容相同的图片才有消除的可能
- 每次只能消除两张图片,消除时需要使用鼠标指定(即连接)
- 两张图片连接时所经过的路径(连接路径)不能超过两个拐点
- 连接路径经过的单元格所包含的图片必须已经消除
直观感受,第一条和第二条规则不应该是算法完成的任务,因为这两条规则实现起来比较简单,应该尽量放在游戏逻辑中完成,避免算法与游戏逻辑产生强依赖关系。实现第三条和第四条规则有一个非常经典的算法理论,该算法就是接下来我们要讲的分类搜索算法。
2 分类搜索算法的原理
分类搜索算法的基本原理是一种递归思想。假设我们要判断A单元与B单元格是否可以通过一条具有N个拐点的路径相连,该问题可以转化为能否找到一个C单元格,C与A可以直线连接(0折连接),且C与B可以通过一条具有N-1个拐点的路径连接。下面截图解释了这一思想。图中,白色和浅灰色的单元格表示没有内容,可以连通。可以发现,A与B连接必须经过①②③④⑤⑥个拐点。假设我们找到了一个可以直接与A连接的C点,那么只需要搜索C与B连接需要经过的②③④⑤⑥个拐点即可。
基于连连看要求的拐点数不能超过2个的规则,我们可以将上述思想简化为三种情况。
1)0折连接
0折连接表示A与B的X坐标或Y坐标相等,可以直线连接,不需要任何拐点,且连通的路径上没有任何阻碍,具体可以分为下面两种情况。
2)1折连接
1折连接与0折连接恰好相反,要求A单元格与B单元格的X轴坐标与Y轴坐标都不能相等。此时通过A与B可以画出一个矩形,而A与B位于矩形的对角点上。判断A与B能否一折连接只需要判断矩形的另外两个对角点是否有一个能同时与A和B满足0折连接。下面截图说明了1折连通的原理:3)2折连接
根据递归的思想,判断A单元格与B单元格能否经过两个拐点连接,可以转化为判断能否找到一个C单元格,该C单元格可以与A单元格0折连接,且C与B可以1折连接。若能找到这样一个C单元格,那么A与B就可以2折连接,下面截图解释了2折连接的情况:
判断A单元格和B单元格是否可以2折连接时需要完成水平和竖直方向上的扫描。观察下面两幅截图,A与B单元格的连接属于典型的2折连接,首先我们需要找到图中的C单元格,然后判断C与B单元格是否可以1折连接。在搜索C单元格时我们必须从A单元格开始,分别向右、向左扫描,寻找同时可以满足与A单元格0折连接,与B单元格1折连接的C单元格。
同样,如果A与B单元格的位置关系是下面两幅截图展示的那样。那么我们就需要在垂直方向完成向上、向下搜索,找到符合要求的C单元格。
3 如何实现通用的分类搜索算法
前面多次强调,我们需要实现一个通用的分类搜索算法。通用意味着算法与具体的实现分离。上面介绍的分类搜索算法建立在一个二维数组的前提下,但是我们应该使用何种类型的二维数组呢?为了满足上述要求,我们应该定义一个所有希望使用该算法的应用都应该实现的一个接口,然后在算法中使用该接口类型的二维数组。
那么该接口应该包含些什么方法呢?根据上面对算法的分析,分类搜索算法唯一需要判断的就是每个单元格的连通性,即单元格是否已经填充。理解了这些内容,下面我们创建该接口。
public interface LinkInterface {
public boolean isEmpt