在上一篇《我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)》中提到的两个类:

  1. GameConf:负责管理游戏的初始化设置信息。
  2. GameService:负责游戏的逻辑实现。
其中GameConf的代码如下:cn\oyp\link\utils\GameConf.java
package cn.oyp.link.utils;

import android.content.Context;

/**
* 保存游戏配置的对象 <br/>
* <br/>
* 关于本代码介绍可以参考一下博客: <a href="http://blog.csdn.net/ouyang_peng">欧阳鹏的CSDN博客</a> <br/>
*/
public class GameConf {
/**
* 连连看的每个方块的图片的宽
*/
public static final int PIECE_WIDTH = 40;
/**
* 连连看的每个方块的图片的高s
*/
public static final int PIECE_HEIGHT = 40;
/**
* 记录游戏的总事件(100秒).
*/
public static int DEFAULT_TIME = 100;
/**
* Piece[][]数组第一维的长度
*/
private int xSize;
/**
* Piece[][]数组第二维的长度
*/
private int ySize;
/**
* Board中第一张图片出现的x座标
*/
private int beginImageX;
/**
* Board中第一张图片出现的y座标
*/
private int beginImageY;
/**
* 记录游戏的总时间, 单位是秒
*/
private long gameTime;
/**
* 应用上下文
*/
private Context context; /**
* 提供一个参数构造器
*
* @param xSize
* Piece[][]数组第一维长度
* @param ySize
* Piece[][]数组第二维长度
* @param beginImageX
* Board中第一张图片出现的x座标
* @param beginImageY
* Board中第一张图片出现的y座标
* @param gameTime
* 设置每局的时间, 单位是豪秒
* @param context
* 应用上下文
*/
public GameConf(int xSize, int ySize, int beginImageX, int beginImageY,
long gameTime, Context context) {
this.xSize = xSize;
this.ySize = ySize;
this.beginImageX = beginImageX;
this.beginImageY = beginImageY;
this.gameTime = gameTime;
this.context = context;
} /**
* @return 游戏的总时间
*/
public long getGameTime() {
return gameTime;
} /**
* @return Piece[][]数组第一维的长度
*/
public int getXSize() {
return xSize;
} /**
* @return Piece[][]数组第二维的长度
*/
public int getYSize() {
return ySize;
} /**
* @return Board中第一张图片出现的x座标
*/
public int getBeginImageX() {
return beginImageX;
} /**
* @return Board中第一张图片出现的y座标
*/
public int getBeginImageY() {
return beginImageY;
} /**
* @return 应用上下文
*/
public Context getContext() {
return context;
}
}

而GameService则是整个游戏逻辑实现的核心,而且GameService是一个可以复用的业务逻辑类,它于游戏平台无关,既可以在Java Swing中使用,也可以在Android游戏中使用,甚至只要稍作修改,GameService也可以移植到C#平台的连连看游戏中。

考虑到程序的可扩展行,先给GameService组件定义一个接口,代码如下:cn\oyp\link\board\GameService.java

package cn.oyp.link.board;

import cn.oyp.link.utils.LinkInfo;
import cn.oyp.link.view.Piece; /**
* 游戏逻辑接口 <br/>
* <br/>
* 关于本代码介绍可以参考一下博客: <a href="http://blog.csdn.net/ouyang_peng">欧阳鹏的CSDN博客</a> <br/>
*/
public interface GameService {
/**
* 控制游戏开始的方法
*/
public void start(); /**
* 定义一个接口方法, 用于返回一个二维数组
*
* @return 存放方块对象的二维数组
*/
public Piece[][] getPieces(); /**
* 判断参数Piece[][]数组中是否还存在非空的Piece对象
*
* @return 如果还剩Piece对象返回true, 没有返回false
*/
public boolean hasPieces(); /**
* 根据鼠标的x座标和y座标, 查找出一个Piece对象
*
* @param touchX
* 鼠标点击的x座标
* @param touchY
* 鼠标点击的y座标
* @return 返回对应的Piece对象, 没有返回null
*/
public Piece findPiece(float touchX, float touchY); /**
* 判断两个Piece是否可以相连, 可以连接, 返回LinkInfo对象
*
* @param p1
* 第一个Piece对象
* @param p2
* 第二个Piece对象
* @return 如果可以相连,返回LinkInfo对象, 如果两个Piece不可以连接, 返回null
*/
public LinkInfo link(Piece p1, Piece p2);
}

下面来具体实现GameService组件,首先的public void start()方法,public Piece[][] getPieces()方法和public boolean hasPieces()方法很容易实现,具体实现如下:cn\oyp\link\board\impl\GameServiceImpl.java

/**
* 游戏逻辑的实现类 <br/>
* <br/>
* 关于本代码介绍可以参考一下博客: <a href="http://blog.csdn.net/ouyang_peng">欧阳鹏的CSDN博客</a> <br/>
*/
public class GameServiceImpl implements GameService {
/**
* 定义一个Piece[][]数组
*/
private Piece[][] pieces;
/**
* 游戏配置对象
*/
private GameConf config; /**
* 构造方法
*
* @param config
* 游戏配置对象
*/
public GameServiceImpl(GameConf config) {
// 将游戏的配置对象设置本类中
this.config = config;
} @Override
public void start() {
// 定义一个AbstractBoard对象
AbstractBoard board = null;
Random random = new Random();
// 获取一个随机数, 可取值0、1、2、3四值。
int index = random.nextInt(4);
// 随机生成AbstractBoard的子类实例
switch (index) {
case 0:
// 0返回VerticalBoard(竖向)
board = new VerticalBoard();
break;
case 1:
// 1返回HorizontalBoard(横向)
board = new HorizontalBoard();
break;
default:
// 默认返回FullBoard
board = new FullBoard();
break;
}
// 初始化Piece[][]数组
this.pieces = board.create(config);
} @Override
public Piece[][] getPieces() {
return this.pieces;
} @Override
public boolean hasPieces() {
// 遍历Piece[][]数组的每个元素
for (int i = 0; i < pieces.length; i++) {
for (int j = 0; j < pieces[i].length; j++) {
// 只要任意一个数组元素不为null,也就是还剩有非空的Piece对象
if (pieces[i][j] != null) {
return true;
}
}
}
return false;
}
...
}

1、获取触碰点的方块

首先当用户碰触游戏界面时,事件监听器获取的是该触碰到在游戏界面上的X、Y坐标,但是程序需要的是获取用户碰触的到底是那个方块,因此程序必须把界面上的X、Y坐标换算成在Piece[][]二维数组中的两个索引值。考虑到游戏界面上每个方块的高度和宽度都是相同的,因此想要将界面上的X、Y坐标换算成Piece[][]二维数组中的索引也比较简单,只要拿X、Y坐标值除以图片的宽、高即可。下面是根据触点X、Y坐标获取对于方块的代码:

/**
* 根据触碰点的位置查找相应的方块
*/
@Override
public Piece findPiece(float touchX, float touchY) {
/*
* 由于在创建Piece对象的时候, 将每个Piece的开始座标加了
* GameConf中设置的beginImageX、beginImageY值, 因此这里要减去这个值
*/
int relativeX = (int) touchX - this.config.getBeginImageX();
int relativeY = (int) touchY - this.config.getBeginImageY();
/*
* 如果鼠标点击的地方比board中第一张图片的开始x座标和开始y座标要小, 即没有找到相应的方块
*/
if (relativeX < 0 || relativeY < 0) {
return null;
}
/*
* 获取relativeX座标在Piece[][]数组中的第一维的索引值 ,第二个参数为每张图片的宽
*/
int indexX = getIndex(relativeX, GameConf.PIECE_WIDTH);
/*
* 获取relativeY座标在Piece[][]数组中的第二维的索引值 ,第二个参数为每张图片的高
*/
int indexY = getIndex(relativeY, GameConf.PIECE_HEIGHT);
// 这两个索引比数组的最小索引还小, 返回null
if (indexX < 0 || indexY < 0) {
return null;
}
// 这两个索引比数组的最大索引还大(或者等于), 返回null
if (indexX >= this.config.getXSize()
|| indexY >= this.config.getYSize()) {
return null;
}
// 返回Piece[][]数组的指定元素
return this.pieces[indexX][indexY];
}

上面的方法调用了getIndex(int relative,int size)方法,该方法的实现就是拿relative除以size,程序需要判断可以整除和不能整除两种情况:如果可以整除,说明还在前一个方块内;如果不能整除,则对于于下一个方块,下面是getIndex(int relative,int size)方法的代码:

/**
* 工具方法:计算相对于Piece[][]数组的第一维 或第二维的索引值
*
* @param relative
* 座标
* @param size
* 每张图片边的长或者宽
* @return
*/
private int getIndex(int relative, int size) {
// 表示座标relative不在该数组中,数组下标从0开始
int index = -1;
/*
* 让座标除以边长, 没有余数, 索引减1, 例如点了x座标为20, 边宽为10, 20 % 10 没有余数, index为1,
* 即在数组中的索引为1(第二个元素)
*/
if (relative % size == 0) {
index = relative / size - 1;
} else {
/*
* 有余数, 例如点了x座标为21, 边宽为10, 21 % 10有余数, index为2, 即在数组中的索引为2(第三个元素)
*/
index = relative / size;
}
return index;
}

2、判断两个方块是否可以相连

两个方块可以相连的情况可以大致分为以下几种:
  • 两个方块位于同一条水平线,可以直接相连。
  • 两个方块位于同一条竖直线,可以直接相连。
  • 两个方块以两条线段相连,也就是有1个拐角。
  • 两个方块以三条线段相连,也就是有2个拐角。
下面的link(Piece p1, Piece p2)方法把这四种情况分开进行处理,代码如下:
@Override
public LinkInfo link(Piece p1, Piece p2) {
// 两个Piece是同一个, 即选中了同一个方块, 返回null
if (p1.equals(p2))
return null;
// 如果p1的图片与p2的图片不相同, 则返回null
if (!p1.isSameImage(p2))
return null;
// 如果p2在p1的左边, 则需要重新执行本方法, 两个参数互换
if (p2.getIndexX() < p1.getIndexX())
return link(p2, p1);
// 获取p1的中心点
Point p1Point = p1.getCenter();
// 获取p2的中心点
Point p2Point = p2.getCenter();
// 情况1:如果两个Piece在同一行,并且可以直接相连
if (p1.getIndexY() == p2.getIndexY()) {
// 它们在同一行并可以相连
if (!isXBlock(p1Point, p2Point, GameConf.PIECE_WIDTH)) {
// 它们之间没有真接障碍, 没有转折点
return new LinkInfo(p1Point, p2Point);
}
}
// 情况2:如果两个Piece在同一列,并且可以直接相连
if (p1.getIndexX() == p2.getIndexX()) {
if (!isYBlock(p1Point, p2Point, GameConf.PIECE_HEIGHT)) {
// 它们之间没有真接障碍, 没有转折点
return new LinkInfo(p1Point, p2Point);
}
}
/*
* 情况3:两个Piece以两条线段相连,也就是有一个转折点的情况。 获取两个点的直角相连的点, 即只有一个转折点
*/
Point cornerPoint = getCornerPoint(p1Point, p2Point,
GameConf.PIECE_WIDTH, GameConf.PIECE_HEIGHT);
// 它们之间有一个转折点
if (cornerPoint != null) {
return new LinkInfo(p1Point, cornerPoint, p2Point);
}
/*
* 情况4:两个Piece以三条线段相连,有两个转折点的情况。 该map的key存放第一个转折点,
* value存放第二个转折点,map的size()说明有多少种可以连的方式
*/
Map<Point, Point> turns = getLinkPoints(p1Point, p2Point,
GameConf.PIECE_WIDTH, GameConf.PIECE_WIDTH);
// 它们之间有转折点
if (turns.size() != 0) {
// 获取p1和p2之间最短的连接信息
return getShortcut(p1Point, p2Point, turns,
getDistance(p1Point, p2Point));
}
return null;
}

3、定义获取通道的方法

所谓通道,指的是一个方块上、下、左、右四个方向上的空白方块,如下图所示:

下面是获取某个坐标点四周通道的四个方法:

/**
* 给一个Point对象,返回它的左边通道
*
* @param p
* @param pieceWidth
* piece图片的宽
* @param min
* 向左遍历时最小的界限
* @return 给定Point左边的通道
*/
private List<Point> getLeftChanel(Point p, int min, int pieceWidth) {
List<Point> result = new ArrayList<Point>();
// 获取向左通道, 由一个点向左遍历, 步长为Piece图片的宽
for (int i = p.x - pieceWidth; i >= min; i = i - pieceWidth) {
// 遇到障碍, 表示通道已经到尽头, 直接返回
if (hasPiece(i, p.y)) {
return result;
}
result.add(new Point(i, p.y));
}
return result;
} /**
* 给一个Point对象, 返回它的右边通道
*
* @param p
* @param pieceWidth
* @param max
* 向右时的最右界限
* @return 给定Point右边的通道
*/
private List<Point> getRightChanel(Point p, int max, int pieceWidth) {
List<Point> result = new ArrayList<Point>();
// 获取向右通道, 由一个点向右遍历, 步长为Piece图片的宽
for (int i = p.x + pieceWidth; i <= max; i = i + pieceWidth) {
// 遇到障碍, 表示通道已经到尽头, 直接返回
if (hasPiece(i, p.y)) {
return result;
}
result.add(new Point(i, p.y));
}
return result;
} /**
* 给一个Point对象, 返回它的上面通道
*
* @param p
* @param min
* 向上遍历时最小的界限
* @param pieceHeight
* @return 给定Point上面的通道
*/
private List<Point> getUpChanel(Point p, int min, int pieceHeight) {
List<Point> result = new ArrayList<Point>();
// 获取向上通道, 由一个点向右遍历, 步长为Piece图片的高
for (int i = p.y - pieceHeight; i >= min; i = i - pieceHeight) {
// 遇到障碍, 表示通道已经到尽头, 直接返回
if (hasPiece(p.x, i)) {
// 如果遇到障碍, 直接返回
return result;
}
result.add(new Point(p.x, i));
}
return result;
} /**
* 给一个Point对象, 返回它的下面通道
*
* @param p
* @param max
* 向上遍历时的最大界限
* @return 给定Point下面的通道
*/
private List<Point> getDownChanel(Point p, int max, int pieceHeight) {
List<Point> result = new ArrayList<Point>();
// 获取向下通道, 由一个点向右遍历, 步长为Piece图片的高
for (int i = p.y + pieceHeight; i <= max; i = i + pieceHeight) {
// 遇到障碍, 表示通道已经到尽头, 直接返回
if (hasPiece(p.x, i)) {
// 如果遇到障碍, 直接返回
return result;
}
result.add(new Point(p.x, i));
}
return result;
}

上面调用到的hasPiece(int x, int y)方法是判断GamePanel中的x, y座标中是否有Piece对象的,代码如下:

/**
* 判断GamePanel中的x, y座标中是否有Piece对象
*
* @param x
* @param y
* @return true 表示有该座标有piece对象 false 表示没有
*/
private boolean hasPiece(int x, int y) {
if (findPiece(x, y) == null)
return false;
return true;
}

4、没有转折点的横向连接

如果两个Piece对象在Piece[][]数组中的第二维索引值相等,那么这两个Piece就在同一行,这时候需要判断两个Piece直接是否有障碍,调用isXBlock(Point p1,Point p2,int pieceWidth)方法,代码如下:

/**
* 判断两个y座标相同的点对象之间是否有障碍, 以p1为中心向右遍历
*
* @param p1
* @param p2
* @param pieceWidth
* 连连看的每个方块的图片的宽
* @return 两个Piece之间有障碍返回true,否则返回false
*/
private boolean isXBlock(Point p1, Point p2, int pieceWidth) {
if (p2.x < p1.x) {
// 如果p2在p1左边, 调换参数位置调用本方法
return isXBlock(p2, p1, pieceWidth);
}
for (int i = p1.x + pieceWidth; i < p2.x; i = i + pieceWidth) {
if (hasPiece(i, p1.y)) {// 有障碍
return true;
}
}
return false;
}

如果两个方块位于同一行,且它们之间没有障碍,那么这两个方块就可以消除,两个方块的连接信息就是它们的中心。

5、没有转折点的纵向连接

如果两个Piece对象在Piece[][]数组中的第一维索引值相等,那么这两个Piece就在同一列,这时候需要判断两个Piece直接是否有障碍,调用isYBlock(Point p1,Point p2,int pieceWidth)方法,代码如下:

/**
* 判断两个x座标相同的点对象之间是否有障碍, 以p1为中心向下遍历
*
* @param p1
* @param p2
* @param pieceHeight
* 连连看的每个方块的图片的高
* @return 两个Piece之间有障碍返回true,否则返回false
*/
private boolean isYBlock(Point p1, Point p2, int pieceHeight) {
if (p2.y < p1.y) {
// 如果p2在p1的上面, 调换参数位置重新调用本方法
return isYBlock(p2, p1, pieceHeight);
}
for (int i = p1.y + pieceHeight; i < p2.y; i = i + pieceHeight) {
if (hasPiece(p1.x, i)) {
// 有障碍
return true;
}
}
return false;
}

如果两个方块位于同一列,且它们之间没有障碍,那么这两个方块就可以消除,两个方块的连接信息就是它们的中心。

6、一个转折点的连接

对于两个方块连接线上只有一个转折点的情况,程序需要先找到这个转折点。为了找到这个转折点,程序定义了一个遍历两个通道并获取它们交点的方法,getWrapPoint(List<Point> p1Chanel, List<Point> p2Chanel),代码如下:

/**
* 遍历两个通道, 获取它们的交点
*
* @param p1Chanel
* 第一个点的通道
* @param p2Chanel
* 第二个点的通道
* @return 两个通道有交点,返回交点,否则返回null
*/
private Point getWrapPoint(List<Point> p1Chanel, List<Point> p2Chanel) {
for (int i = 0; i < p1Chanel.size(); i++) {
Point temp1 = p1Chanel.get(i);
for (int j = 0; j < p2Chanel.size(); j++) {
Point temp2 = p2Chanel.get(j);
if (temp1.equals(temp2)) {
// 如果两个List中有元素有同一个, 表明这两个通道有交点
return temp1;
}
}
}
return null;
}

为了找出两个方块连接线上的连接点,程序需要分析p1和p2的位置分布。所以我们可以分析p2要么在p1的右上角,要么在p1的右下角。至于p2位于p1的左上角和左下角的情况,只要将p1、p2交换即可,如下图所示:

  • 当p2位于p1右上角时候,应该计算p1的右通道和p2的下通道是否有交点,p1的上通道和p2的左通道是否有交点。
  • 当p2位于p1右下角时候,应该计算p1的右通道和p2的上通道是否有交点,p1的下通道和p2的左通道是否有交点。
下面是具体是实现方法getCornerPoint(Point point1, Point point2, int pieceWidth,
int pieceHeight)的代码:
/**
* 获取两个不在同一行或者同一列的座标点的直角连接点, 即只有一个转折点
*
* @param point1
* 第一个点
* @param point2
* 第二个点
* @return 两个不在同一行或者同一列的座标点的直角连接点
*/
private Point getCornerPoint(Point point1, Point point2, int pieceWidth,
int pieceHeight) {
// 先判断这两个点的位置关系, 如果point2在point1的左上角或者 point2在point1的左下角
if (isLeftUp(point1, point2) || isLeftDown(point1, point2)) {
// 参数换位, 重新调用本方法
return getCornerPoint(point2, point1, pieceWidth, pieceHeight);
}
// 获取p1向右的通道
List<Point> point1RightChanel = getRightChanel(point1, point2.x,
pieceWidth);
// 获取p1向上的通道
List<Point> point1UpChanel = getUpChanel(point1, point2.y, pieceHeight);
// 获取p1向下的通道
List<Point> point1DownChanel = getDownChanel(point1, point2.y,
pieceHeight);
// 获取p2向下的通道
List<Point> point2DownChanel = getDownChanel(point2, point1.y,
pieceHeight);
// 获取p2向左的通道
List<Point> point2LeftChanel = getLeftChanel(point2, point1.x,
pieceWidth);
// 获取p2向上的通道
List<Point> point2UpChanel = getUpChanel(point2, point1.y, pieceHeight);
// 如果point2在point1的右上角
if (isRightUp(point1, point2)) {
// 获取p1向右和p2向下的交点
Point linkPoint1 = getWrapPoint(point1RightChanel, point2DownChanel);
// 获取p1向上和p2向左的交点
Point linkPoint2 = getWrapPoint(point1UpChanel, point2LeftChanel);
// 返回其中一个交点, 如果没有交点, 则返回null
return (linkPoint1 == null) ? linkPoint2 : linkPoint1;
}
/**********************************************************/
// 如果point2在point1的右下角
if (isRightDown(point1, point2)) {
// point2在point1的右下角
// 获取p1向下和p2向左的交点
Point linkPoint1 = getWrapPoint(point1DownChanel, point2LeftChanel);
// 获取p1向右和p2向下的交点
Point linkPoint2 = getWrapPoint(point1RightChanel, point2UpChanel);
return (linkPoint1 == null) ? linkPoint2 : linkPoint1;
}
return null;
}

上面方法调用了以下四个方法:

/**
* 判断point2是否在point1的左上角
*
* @param point1
* @param point2
* @return p2位于p1的左上角时返回true,否则返回false
*/
private boolean isLeftUp(Point point1, Point point2) {
return (point2.x < point1.x && point2.y < point1.y);
} /**
* 判断point2是否在point1的左下角
*
* @param point1
* @param point2
* @return p2位于p1的左下角时返回true,否则返回false
*/
private boolean isLeftDown(Point point1, Point point2) {
return (point2.x < point1.x && point2.y > point1.y);
} /**
* 判断point2是否在point1的右上角
*
* @param point1
* @param point2
* @return p2位于p1的右上角时返回true,否则返回false
*/
private boolean isRightUp(Point point1, Point point2) {
return (point2.x > point1.x && point2.y < point1.y);
} /**
* 判断point2是否在point1的右下角
*
* @param point1
* @param point2
* @return p2位于p1的右下角时返回true,否则返回false
*/
private boolean isRightDown(Point point1, Point point2) {
return (point2.x > point1.x && point2.y > point1.y);
}

7、两个转折点的连接

两个转折点可以分为以下几种情况讨论:

  • p1、p2位于同一行,不能直接相连,就必须有两个转折点,分向上和向下两种连接情况。
  • p1、p2位于同一行,不能直接相连,就必须有两个转折点,分向左和向右两种连接情况。
  • p2在p1的右下角,有6中转折情况。
  • p2在p1的右上角,也有6种转折情况。
至于p2位于p1的左上角和左下角的情况,只要将p1、p2交换即可。

1)、p1、p2位于同一行,不能直接相连,就必须有两个转折点,如下图所示


当p1与p2位于同一行不能直接相连,这两个点既可以在上面相连,也可以在下面相连,这两种情况都代表他们可以相连,先把这两种情况加入到结果中,最后去计算最近的距离。

实现时先构建一个Map,Map的key为第一个转折点,Map的value为第二个转折点,如果Map的size()大于1,说明这两个Point有多种连接途径,那么程序还需要计算路径最小的连接方式。

2)p1、p2位于同一行,不能直接相连,就必须有两个转折点,如上图所示。

当p1与p2位于同一列不能直接相连,这两个点既可以在左边相连,也可以在右边相连,这两种情况都代表他们可以相连,先把这两种情况加入到结果中,最后去计算最近的距离。

实现时先构建一个Map,Map的key为第一个转折点,Map的value为第二个转折点,如果Map的size()大于1,说明这两个Point有多种连接途径,那么程序还需要计算路径最小的连接方式。

3)p2位于p1右下角的六种转折情况,如下图所示:



定义一个方法来处理上面具有两个连接点的情况,getLinkPoints(Point point1, Point point2,
int pieceWidth, int pieceHeight),代码如下所示:
/**
* 获取两个转折点的情况
*
* @param point1
* @param point2
* @return Map对象的每个key-value对代表一种连接方式, 其中key、value分别代表第1个、第2个连接点
*/
private Map<Point, Point> getLinkPoints(Point point1, Point point2,
int pieceWidth, int pieceHeight) {
Map<Point, Point> result = new HashMap<Point, Point>(); // 获取以point1为中心的向上的通道
List<Point> p1UpChanel = getUpChanel(point1, point2.y, pieceHeight);
// 获取以point1为中心的向右的通道
List<Point> p1RightChanel = getRightChanel(point1, point2.x, pieceWidth);
// 获取以point1为中心的向下的通道
List<Point> p1DownChanel = getDownChanel(point1, point2.y, pieceHeight);
// 获取以point2为中心的向下的通道
List<Point> p2DownChanel = getDownChanel(point2, point1.y, pieceHeight);
// 获取以point2为中心的向左的通道
List<Point> p2LeftChanel = getLeftChanel(point2, point1.x, pieceWidth);
// 获取以point2为中心的向上的通道
List<Point> p2UpChanel = getUpChanel(point2, point1.y, pieceHeight); // 获取Board的最大高度
int heightMax = (this.config.getYSize() + 1) * pieceHeight
+ this.config.getBeginImageY();
// 获取Board的最大宽度
int widthMax = (this.config.getXSize() + 1) * pieceWidth
+ this.config.getBeginImageX();
/*
* 先确定两个点的关系,如果 point2在point1的左上角或者左下角
*/
if (isLeftUp(point1, point2) || isLeftDown(point1, point2)) {
// 参数换位, 调用本方法
return getLinkPoints(point2, point1, pieceWidth, pieceHeight);
}
// 情况1:如果p1、p2位于同一行而不能直接相连,需要两个转折点,可以在上面相连也可以在下面相连
if (point1.y == point2.y) {// 在同一行
// 第1步: 向上遍历
// 以p1的中心点向上遍历获取点集合
p1UpChanel = getUpChanel(point1, 0, pieceHeight);
// 以p2的中心点向上遍历获取点集合
p2UpChanel = getUpChanel(point2, 0, pieceHeight);
// 如果两个集合向上中有Y坐标相同,即在同一行,且之间没有障碍物
Map<Point, Point> upLinkPoints = getXLinkPoints(p1UpChanel,
p2UpChanel, pieceHeight); // 第2步: 向下遍历, 不超过Board(有方块的地方)的边框
// 以p1中心点向下遍历获取点集合
p1DownChanel = getDownChanel(point1, heightMax, pieceHeight);
// 以p2中心点向下遍历获取点集合
p2DownChanel = getDownChanel(point2, heightMax, pieceHeight);
// 如果两个集合向上中有Y坐标相同,即在同一行,且之间没有障碍物
Map<Point, Point> downLinkPoints = getXLinkPoints(p1DownChanel,
p2DownChanel, pieceHeight);
result.putAll(upLinkPoints);
result.putAll(downLinkPoints);
}
// 情况2:p1、p2位于同一列不能直接相连,需要两个转折点,可以在左边相连也可以在右边相连
if (point1.x == point2.x) {// 在同一列
// 第1步:向左遍历
// 以p1的中心点向左遍历获取点集合
List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth);
// 以p2的中心点向左遍历获取点集合
p2LeftChanel = getLeftChanel(point2, 0, pieceWidth);
// 如果两个集合向上中有X坐标相同,即在同一列,且之间没有障碍物
Map<Point, Point> leftLinkPoints = getYLinkPoints(p1LeftChanel,
p2LeftChanel, pieceWidth); // 第2步:向右遍历, 不得超过Board的边框(有方块的地方)
// 以p1的中心点向右遍历获取点集合
p1RightChanel = getRightChanel(point1, widthMax, pieceWidth);
// 以p2的中心点向右遍历获取点集合
List<Point> p2RightChanel = getRightChanel(point2, widthMax,
pieceWidth);
// 如果两个集合向上中有X坐标相同,即在同一列,且之间没有障碍物
Map<Point, Point> rightLinkPoints = getYLinkPoints(p1RightChanel,
p2RightChanel, pieceWidth);
result.putAll(leftLinkPoints);
result.putAll(rightLinkPoints);
}
// 情况3:point2位于point1的右上角,分六种情况讨论
if (isRightUp(point1, point2)) {
//第1步: 获取point1向上遍历, point2向下遍历时横向可以连接的点
Map<Point, Point> upDownLinkPoints = getXLinkPoints(p1UpChanel,
p2DownChanel, pieceWidth);
/**********************************************************/
//第2步:获取point1向右遍历, point2向左遍历时纵向可以连接的点
Map<Point, Point> rightLeftLinkPoints = getYLinkPoints(
p1RightChanel, p2LeftChanel, pieceHeight);
/**********************************************************/
// 获取以p1为中心的向上通道
p1UpChanel = getUpChanel(point1, 0, pieceHeight);
// 获取以p2为中心的向上通道
p2UpChanel = getUpChanel(point2, 0, pieceHeight);
//第3步: 获取point1向上遍历, point2向上遍历时横向可以连接的点
Map<Point, Point> upUpLinkPoints = getXLinkPoints(p1UpChanel,
p2UpChanel, pieceWidth);
/**********************************************************/
// 获取以p1为中心的向下通道
p1DownChanel = getDownChanel(point1, heightMax, pieceHeight);
// 获取以p2为中心的向下通道
p2DownChanel = getDownChanel(point2, heightMax, pieceHeight);
//第4步: 获取point1向下遍历, point2向下遍历时横向可以连接的点
Map<Point, Point> downDownLinkPoints = getXLinkPoints(p1DownChanel,
p2DownChanel, pieceWidth);
/**********************************************************/
// 获取以p1为中心的向右通道
p1RightChanel = getRightChanel(point1, widthMax, pieceWidth);
// 获取以p2为中心的向右通道
List<Point> p2RightChanel = getRightChanel(point2, widthMax,
pieceWidth);
//第5步:获取point1向右遍历, point2向右遍历时纵向可以连接的点
Map<Point, Point> rightRightLinkPoints = getYLinkPoints(
p1RightChanel, p2RightChanel, pieceHeight);
/**********************************************************/
// 获取以p1为中心的向左通道
List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth);
// 获取以p2为中心的向左通道
p2LeftChanel = getLeftChanel(point2, 0, pieceWidth);
//第6步: 获取point1向左遍历, point2向左遍历时纵向可以连接的点
Map<Point, Point> leftLeftLinkPoints = getYLinkPoints(p1LeftChanel,
p2LeftChanel, pieceHeight);
/**********************************************************/
result.putAll(upDownLinkPoints);
result.putAll(rightLeftLinkPoints);
result.putAll(upUpLinkPoints);
result.putAll(downDownLinkPoints);
result.putAll(rightRightLinkPoints);
result.putAll(leftLeftLinkPoints);
}
// 情况4:point2位于point1的右下角,分六种情况讨论
if (isRightDown(point1, point2)) {
//第1步: 获取point1向下遍历, point2向上遍历时横向可连接的点
Map<Point, Point> downUpLinkPoints = getXLinkPoints(p1DownChanel,
p2UpChanel, pieceWidth);
/**********************************************************/
//第2步: 获取point1向右遍历, point2向左遍历时纵向可连接的点
Map<Point, Point> rightLeftLinkPoints = getYLinkPoints(
p1RightChanel, p2LeftChanel, pieceHeight);
/**********************************************************/
// 获取以p1为中心的向上通道
p1UpChanel = getUpChanel(point1, 0, pieceHeight);
// 获取以p2为中心的向上通道
p2UpChanel = getUpChanel(point2, 0, pieceHeight);
//第3步: 获取point1向上遍历, point2向上遍历时横向可连接的点
Map<Point, Point> upUpLinkPoints = getXLinkPoints(p1UpChanel,
p2UpChanel, pieceWidth);
/**********************************************************/
// 获取以p1为中心的向下通道
p1DownChanel = getDownChanel(point1, heightMax, pieceHeight);
// 获取以p2为中心的向下通道
p2DownChanel = getDownChanel(point2, heightMax, pieceHeight);
//第4步: 获取point1向下遍历, point2向下遍历时横向可连接的点
Map<Point, Point> downDownLinkPoints = getXLinkPoints(p1DownChanel,
p2DownChanel, pieceWidth);
/**********************************************************/
// 获取以p1为中心的向左通道
List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth);
// 获取以p2为中心的向左通道
p2LeftChanel = getLeftChanel(point2, 0, pieceWidth);
//第5步: 获取point1向左遍历, point2向左遍历时纵向可连接的点
Map<Point, Point> leftLeftLinkPoints = getYLinkPoints(p1LeftChanel,
p2LeftChanel, pieceHeight);
/**********************************************************/
// 获取以p1为中心的向右通道
p1RightChanel = getRightChanel(point1, widthMax, pieceWidth);
// 获取以p2为中心的向右通道
List<Point> p2RightChanel = getRightChanel(point2, widthMax,
pieceWidth);
//第6步: 获取point1向右遍历, point2向右遍历时纵向可以连接的点
Map<Point, Point> rightRightLinkPoints = getYLinkPoints(
p1RightChanel, p2RightChanel, pieceHeight);
/**********************************************************/
result.putAll(downUpLinkPoints);
result.putAll(rightLeftLinkPoints);
result.putAll(upUpLinkPoints);
result.putAll(downDownLinkPoints);
result.putAll(leftLeftLinkPoints);
result.putAll(rightRightLinkPoints);
}
return result;
}

上面调用的getXLinkPoints、getYLinkPoints方法代码如下:

/**
* 遍历两个集合, 先判断第一个集合的元素的x座标与另一个集合中的元素x座标相同(纵向), 如果相同, 即在同一列, 再判断是否有障碍,
* 没有则加到结果的Map中去
*
* @param p1Chanel
* @param p2Chanel
* @param pieceHeight
* @return
*/
private Map<Point, Point> getYLinkPoints(List<Point> p1Chanel,
List<Point> p2Chanel, int pieceHeight) {
Map<Point, Point> result = new HashMap<Point, Point>();
for (int i = 0; i < p1Chanel.size(); i++) {
Point temp1 = p1Chanel.get(i);
for (int j = 0; j < p2Chanel.size(); j++) {
Point temp2 = p2Chanel.get(j);
// 如果x座标相同(在同一列)
if (temp1.x == temp2.x) {
// 没有障碍, 放到map中去
if (!isYBlock(temp1, temp2, pieceHeight)) {
result.put(temp1, temp2);
}
}
}
}
return result;
} /**
* 遍历两个集合, 先判断第一个集合的元素的y座标与另一个集合中的元素y座标相同(横向), 如果相同, 即在同一行, 再判断是否有障碍, 没有
* 则加到结果的map中去
*
* @param p1Chanel
* @param p2Chanel
* @param pieceWidth
* @return 存放可以横向直线连接的连接点的键值对
*/
private Map<Point, Point> getXLinkPoints(List<Point> p1Chanel,
List<Point> p2Chanel, int pieceWidth) {
Map<Point, Point> result = new HashMap<Point, Point>();
for (int i = 0; i < p1Chanel.size(); i++) {
// 从第一通道中取一个点
Point temp1 = p1Chanel.get(i);
// 再遍历第二个通道, 看下第二通道中是否有点可以与temp1横向相连
for (int j = 0; j < p2Chanel.size(); j++) {
Point temp2 = p2Chanel.get(j);
// 如果y座标相同(在同一行), 再判断它们之间是否有直接障碍
if (temp1.y == temp2.y) {
if (!isXBlock(temp1, temp2, pieceWidth)) {
// 没有障碍则直接加到结果的map中
result.put(temp1, temp2);
}
}
}
}
return result;
}

8、找出最短距离

为了找出所有连接情况中的最短路径,程序可以分为以下2步骤来实现:
  • 遍历转折点Map中的所有key-value对,与原来选择的两个点构成一个LinkInfo。每个LinkInfo代表一条完整的连接路径,并将这些LinkInfo搜集成一个List集合。
  • 遍历第一步得到的List<LinkInfo>集合,计算每个LinkInfo中连接全部连接点的总距离,选与最短距离相差最小的LinkInfo返回。
/**
* 获取p1和p2之间最短的连接信息
*
* @param p1
* @param p2
* @param turns
* 放转折点的map
* @param shortDistance
* 两点之间的最短距离
* @return p1和p2之间最短的连接信息
*/
private LinkInfo getShortcut(Point p1, Point p2, Map<Point, Point> turns,
int shortDistance) {
List<LinkInfo> infos = new ArrayList<LinkInfo>();
// 遍历结果Map,
for (Point point1 : turns.keySet()) {
Point point2 = turns.get(point1);
// 将转折点与选择点封装成LinkInfo对象, 放到List集合中
infos.add(new LinkInfo(p1, point1, point2, p2));
}
return getShortcut(infos, shortDistance);
} /**
* 从infos中获取连接线最短的那个LinkInfo对象
*
* @param infos
* @return 连接线最短的那个LinkInfo对象
*/
private LinkInfo getShortcut(List<LinkInfo> infos, int shortDistance) {
int temp1 = 0;
LinkInfo result = null;
for (int i = 0; i < infos.size(); i++) {
LinkInfo info = infos.get(i);
// 计算出几个点的总距离
int distance = countAll(info.getLinkPoints());
// 将循环第一个的差距用temp1保存
if (i == 0) {
temp1 = distance - shortDistance;
result = info;
}
// 如果下一次循环的值比temp1的还小, 则用当前的值作为temp1
if (distance - shortDistance < temp1) {
temp1 = distance - shortDistance;
result = info;
}
}
return result;
}

/**
* 计算List<Point>中所有点的距离总和
*
* @param points
* 需要计算的连接点
* @return 所有点的距离的总和
*/
private int countAll(List<Point> points) {
int result = 0;
for (int i = 0; i < points.size() - 1; i++) {
// 获取第i个点
Point point1 = points.get(i);
// 获取第i + 1个点
Point point2 = points.get(i + 1);
// 计算第i个点与第i + 1个点的距离,并添加到总距离中
result += getDistance(point1, point2);
}
return result;
} /**
* 获取两个LinkPoint之间的最短距离
*
* @param p1
* 第一个点
* @param p2
* 第二个点
* @return 两个点的距离距离总和
*/
private int getDistance(Point p1, Point p2) {
int xDistance = Math.abs(p1.x - p2.x);
int yDistance = Math.abs(p1.y - p2.y);
return xDistance + yDistance;
}

关于具体的实现步骤,请参考下面的链接:

==================================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/ouyang_peng

==================================================================================================

我的Android进阶之旅------>Android疯狂连连看游戏的实现之实现游戏逻辑(五)的更多相关文章

  1. 我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)

    正如在<我的Android进阶之旅------>Android疯狂连连看游戏的实现之状态数据模型(三)>一文中看到的,在AbstractBoard的代码中,当程序需要创建N个Piec ...

  2. 我的Android进阶之旅------>Android疯狂连连看游戏的实现之状态数据模型(三)

    对于游戏玩家而言,游戏界面上看到的"元素"千变万化:但是对于游戏开发者而言,游戏界面上的元素在底层都是一些数据,不同数据所绘制的图片有所差异而已.因此建立游戏的状态数据模型是实现游 ...

  3. 我的Android进阶之旅------>Android疯狂连连看游戏的实现之开发游戏界面(二)

    连连看的游戏界面十分简单,大致可以分为两个区域: 游戏主界面区 控制按钮和数据显示区 1.开发界面布局 本程序使用一个RelativeLayout作为整体的界面布局元素,界面布局上面是一个自定义组件, ...

  4. 我的Android进阶之旅------>Android疯狂连连看游戏的实现之游戏效果预览(一)

    今天看完了李刚老师的<疯狂Android讲义>一书中的第18章<疯狂连连看>,从而学会了如何编写一个简单的Android疯狂连连看游戏. 开发这个流行的小游戏,难度适中,而且能 ...

  5. 我的Android进阶之旅------>Android利用Sensor(传感器)实现水平仪功能的小例

    这里介绍的水平仪,指的是比较传统的气泡水平仪,在一个透明圆盘内充满液体,液体中留有一个气泡,当一端翘起时,该气泡就会浮向翘起的一端.    利用方向传感器返回的第一个参数,实现了一个指南针小应用. 我 ...

  6. 我的Android进阶之旅------>Android颜色值(#AARRGGBB)透明度百分比和十六进制对应关系以及计算方法

    我的Android进阶之旅-->Android颜色值(RGB)所支持的四种常见形式 透明度百分比和十六进制对应关系表格 透明度 十六进制 100% FF 99% FC 98% FA 97% F7 ...

  7. 我的Android进阶之旅------>Android中查看应用签名信息

    一.查看自己的证书签名信息 如上一篇文章<我的Android进阶之旅------>Android中制作和查看自定义的Debug版本Android签名证书>地址:http://blog ...

  8. 我的Android进阶之旅------>Android利用温度传感器实现带动画效果的电子温度计

    要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...

  9. 我的Android进阶之旅------>Android实现用Android手机控制PC端的关机和重启的功能(三)Android客户端功能实现

    我的Android进阶之旅------>Android实现用Android手机控制PC端的关机和重启的功能(一)PC服务器端(地址:http://blog.csdn.net/ouyang_pen ...

随机推荐

  1. UVa 10192 - Vacation &amp; UVa 10066 The Twin Towers ( LCS 最长公共子串)

    链接:UVa 10192 题意:给定两个字符串.求最长公共子串的长度 思路:这个是最长公共子串的直接应用 #include<stdio.h> #include<string.h> ...

  2. [Tools] Support VS Code Navigation and Autocomplete Based on Webpack Aliases with jsconfig.json

    It's common to setup Webpack aliases to make imports much more convenient, but then you lose the abi ...

  3. H5性能调优

    概述 PC优化手段在Mobile侧同样适用 在Mobile侧我们提出三秒种渲染完成首屏指标 基于第二点,首屏加载3秒完成或使用Loading 基于联通3G网络平均338KB/s(2.71Mb/s),所 ...

  4. HTML5 Canvas 描画渐开线

    渐开线(evolent):在平面上,一条动直线(发生线)沿着一个固定的圆(基圆)作滚动的过程中,此直线上任意一点的轨迹,称为此基圆的一条渐开线.如果将一个圆轴固定在一个平面上,轴上缠线,拉紧一个线头, ...

  5. mysql binlog 使用

    用于数据恢复的binlog 前提条件 1.定时mysqldumps全备数据库 2.开启binlog增量备份 情景:手滑误操作删表操作 立刻 mysql>flush logs;  #开启一个新的b ...

  6. 内网IPC$种马的三种方法

    copy muma.exe \\host\c$\windows\temp\foobar.exe ##IPC拷贝木马文件 WMIC远程运行命令 wmic /node:host /user:adminis ...

  7. MVVM模式源码分析手写实现

    1.demo1.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...

  8. smali语法高亮相关链接

    http://ruby-china.org/topics/8307 http://www.daqianduan.com/4820.html http://www.cnblogs.com/ruochen ...

  9. Unhandled event loop exception No more handles 解决办法

    1 http://stackoverflow.com/questions/9074189/unhandled-event-loop-exception-in-plugin-org-eclipse-ui ...

  10. url中带有加号的处理方法

    最近项目中出现了一个问题,图片的路径正确,但是转成URL之后无法找到... 找了各种原因之后,最后注意到URL中的图片名称和本地路径名称有点不一样,如下图 1.URL图片 2.本地路径 上网查了一下发 ...