标签:通道 info 学习 point java bsp nbsp code map对象
GameService组件则是整个游戏逻辑实现的核心,而且GameService是一个可以复用的业务逻辑类。
(一)定义GameService组件接口
根据前面程序对GameService组件的依赖,程序需要GameService组件包含如下方法。
·start():初始化游戏状态,开始游戏的方法。
·Piece[][] getPieces():返回表示游戏状态的Piece[][]数组。
·boolean hasPieces():判断Pieces[][]数组中是否还剩Piece对象;如果所有Piece都被消除了,游戏也就胜利了。
·Piece findPiece(float touchX,float touchY):根据触碰点的X、Y坐标来获取。
·LinkInfo link(Piece p1,Piece p2):判断p1、p2两个方块是否可以相连。
为了考虑以后的可拓展性,需先为GameService组件定义如下接口。
接口代码如下:src\org\crazyit\link\board\GameService
1 public interface GameService 2 { 3 /** 4 * 控制游戏开始的方法 5 */ 6 void start(); 7 8 /** 9 * 定义一个接口方法, 用于返回一个二维数组 10 * 11 * @return 存放方块对象的二维数组 12 */ 13 Piece[][] getPieces(); 14 15 /** 16 * 判断参数Piece[][]数组中是否还存在非空的Piece对象 17 * 18 * @return 如果还剩Piece对象返回true, 没有返回false 19 */ 20 boolean hasPieces(); 21 22 /** 23 * 根据鼠标的x座标和y座标, 查找出一个Piece对象 24 * 25 * @param touchX 鼠标点击的x座标 26 * @param touchY 鼠标点击的y座标 27 * @return 返回对应的Piece对象, 没有返回null 28 */ 29 Piece findPiece(float touchX, float touchY); 30 31 /** 32 * 判断两个Piece是否可以相连, 可以连接, 返回LinkInfo对象 33 * 34 * @param p1 第一个Piece对象 35 * @param p2 第二个Piece对象 36 * @return 如果可以相连,返回LinkInfo对象, 如果两个Piece不可以连接, 返回null 37 */ 38 LinkInfo link(Piece p1, Piece p2); 39 }
(二)实现GameService组件
GameService组件的前面三个方法实现起来都比较简单。
前3个方法的代码如下:src\org\crazyit\link\board\impl\GameServiceImpl
1 public class GameServiceImpl implements GameService 2 { 3 // 定义一个Piece[][]数组,只提供getter方法 4 private Piece[][] pieces; 5 // 游戏配置对象 6 private GameConf config; 7 8 public GameServiceImpl(GameConf config) 9 { 10 // 将游戏的配置对象设置本类中 11 this.config = config; 12 } 13 14 @Override 15 public void start() 16 { 17 // 定义一个AbstractBoard对象 18 AbstractBoard board = null; 19 Random random = new Random(); 20 // 获取一个随机数, 可取值0、1、2、3四值。 21 int index = random.nextInt(4); 22 // 随机生成AbstractBoard的子类实例 23 switch (index) 24 { 25 case 0: 26 // 0返回VerticalBoard(竖向) 27 board = new VerticalBoard(); 28 break; 29 case 1: 30 // 1返回HorizontalBoard(横向) 31 board = new HorizontalBoard(); 32 break; 33 default: 34 // 默认返回FullBoard 35 board = new FullBoard(); 36 break; 37 } 38 // 初始化Piece[][]数组 39 this.pieces = board.create(config); 40 } 41 42 // 直接返回本对象的Piece[][]数组 43 @Override 44 public Piece[][] getPieces() 45 { 46 return this.pieces; 47 } 48 49 // 实现接口的hasPieces方法 50 @Override 51 public boolean hasPieces() 52 { 53 // 遍历Piece[][]数组的每个元素 54 for (int i = 0; i < pieces.length; i++) 55 { 56 for (int j = 0; j < pieces[i].length; j++) 57 { 58 // 只要任意一个数组元素不为null,也就是还剩有非空的Piece对象 59 if (pieces[i][j] != null) 60 { 61 return true; 62 } 63 } 64 } 65 return false; 66 } 67 ..... 68 }
前面3个方法实现得很简单。下面会详细介绍后面的两个方法findPiece(float touchX,float touchY)和link(Piece p1,Piece p2)。
(三)获取触碰点的方块
当用户触碰游戏界面时,事件监听器获取的时该触碰点在游戏界面上的X、Y坐标,但程序需要获取用户触碰的是哪块方块,就要把获取的X、Y坐标换算成Piece[][]二维数组中的两个索引值。
考虑到游戏界面上每个方块的宽度、高度都是相同的,因此将获取得X、Y坐标除以图片得宽、高即可换算成Piece[][]二维数组中的索引。
根据触碰点X、Y坐标获取对应方块得代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 // 根据触碰点的位置查找相应的方块 2 @Override 3 public Piece findPiece(float touchX, float touchY) 4 { 5 // 由于在创建Piece对象的时候, 将每个Piece的开始座标加了 6 // GameConf中设置的beginImageX/beginImageY值, 因此这里要减去这个值 7 int relativeX = (int) touchX - this.config.getBeginImageX(); 8 int relativeY = (int) touchY - this.config.getBeginImageY(); 9 // 如果鼠标点击的地方比board中第一张图片的开始x座标和开始y座标要小, 即没有找到相应的方块 10 if (relativeX < 0 || relativeY < 0) 11 { 12 return null; 13 } 14 // 获取relativeX座标在Piece[][]数组中的第一维的索引值 15 // 第二个参数为每张图片的宽 16 int indexX = getIndex(relativeX, GameConf.PIECE_WIDTH); 17 // 获取relativeY座标在Piece[][]数组中的第二维的索引值 18 // 第二个参数为每张图片的高 19 int indexY = getIndex(relativeY, GameConf.PIECE_HEIGHT); 20 // 这两个索引比数组的最小索引还小, 返回null 21 if (indexX < 0 || indexY < 0) 22 { 23 return null; 24 } 25 // 这两个索引比数组的最大索引还大(或者等于), 返回null 26 if (indexX >= this.config.getXSize() 27 || indexY >= this.config.getYSize()) 28 { 29 return null; 30 } 31 // 返回Piece[][]数组的指定元素 32 return this.pieces[indexX][indexY]; 33 }
上面得代码根据触碰点X、Y坐标来计算它在Piece[][]数组中得索引值。调用了getIndex(int relative,int size)进行计算。
getIndex(int relative,int size)方法的实现就是拿relative除以size,只是程序需要判断可以整除和不能整除两种情况:如果可以整除,说明还在前一块方块内;如果不能整除,则对应于下一块方块。
getIndex(int relative,int size)方法的代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 // 工具方法, 根据relative座标计算相对于Piece[][]数组的第一维 2 // 或第二维的索引值 ,size为每张图片边的长或者宽 3 private int getIndex(int relative, int size) 4 { 5 // 表示座标relative不在该数组中 6 int index = -1; 7 // 让座标除以边长, 没有余数, 索引减1 8 // 例如点了x座标为20, 边宽为10, 20 % 10 没有余数, 9 // index为1, 即在数组中的索引为1(第二个元素) 10 if (relative % size == 0) 11 { 12 index = relative / size - 1; 13 } 14 else 15 { 16 // 有余数, 例如点了x座标为21, 边宽为10, 21 % 10有余数, index为2 17 // 即在数组中的索引为2(第三个元素) 18 index = relative / size; 19 } 20 return index; 21 }
(四)判断两个方块是否可以相连
判断两个方块是否可以相连是本程序需要处理的最繁琐的地方:两个方块可以相连的情形比较多,大致可分为:
·两个方块位于同一条水平线,可以直接相连。
·两个方块位于同一条竖直线,可以直接相连。
·两个方块以两条线段相连,有1个拐点。
·两个方块以三条线段相连,有2个拐点。
下面link(Piece p1,Piece p2)方法把这四种情况分开进行处理。
代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 // 实现接口的link方法 2 @Override 3 public LinkInfo link(Piece p1, Piece p2) 4 { 5 // 两个Piece是同一个, 即选中了同一个方块, 返回null 6 if (p1.equals(p2)) 7 return null; 8 // 如果p1的图片与p2的图片不相同, 则返回null 9 if (!p1.isSameImage(p2)) 10 return null; 11 // 如果p2在p1的左边, 则需要重新执行本方法, 两个参数互换 12 if (p2.getIndexX() < p1.getIndexX()) 13 return link(p2, p1); 14 // 获取p1的中心点 15 Point p1Point = p1.getCenter(); 16 // 获取p2的中心点 17 Point p2Point = p2.getCenter(); 18 // 如果两个Piece在同一行 19 if (p1.getIndexY() == p2.getIndexY()) 20 { 21 // 它们在同一行并可以相连 22 if (!isXBlock(p1Point, p2Point, GameConf.PIECE_WIDTH)) 23 { 24 return new LinkInfo(p1Point, p2Point); 25 } 26 } 27 // 如果两个Piece在同一列 28 if (p1.getIndexX() == p2.getIndexX()) 29 { 30 if (!isYBlock(p1Point, p2Point, GameConf.PIECE_HEIGHT)) 31 { 32 // 它们之间没有真接障碍, 没有转折点 33 return new LinkInfo(p1Point, p2Point); 34 } 35 } 36 // 有一个转折点的情况 37 // 获取两个点的直角相连的点, 即只有一个转折点 38 Point cornerPoint = getCornerPoint(p1Point, p2Point, 39 GameConf.PIECE_WIDTH, GameConf.PIECE_HEIGHT); 40 if (cornerPoint != null) 41 { 42 return new LinkInfo(p1Point, cornerPoint, p2Point); 43 } 44 // 该map的key存放第一个转折点, value存放第二个转折点, 45 // map的size()说明有多少种可以连的方式 46 Map<Point, Point> turns = getLinkPoints(p1Point, p2Point, 47 GameConf.PIECE_WIDTH, GameConf.PIECE_WIDTH); 48 if (turns.size() != 0) 49 { 50 return getShortcut(p1Point, p2Point, turns, 51 getDistance(p1Point, p2Point)); 52 } 53 return null; 54 }
上面的代码就前面提到的4种情况,对应了4个不同的方法。我们需要为这4个方法提供实现。
为了实现上面4个方法,可以对两个Piece的位置关系进行归纳。
·p1于p2在同一行(indexY值相同)。
·p1与p2在同一列(indexX值相同)。
·p2在p1的右上角(p2的indexX>p1的indexX,p2的indexY<p1的indexY)。
·p2的p1的右下角(p2的indexX>p1的indexX,p2的indexY>p1的indexY)。
至于p2在p1的左上角,或者p2在p1的左下角这两种情况,程序可以重新执行link方法,将p1和p2两个参数的位置互换即可。
(五)定义获取通道的工具方法
这里所谓的通到,指的是一个方块上、下、左、右四个方向的空白方块。
下面是获取某个坐标点四周通道的4个方法的代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 /** 2 * 给一个Point对象,返回它的左边通道 3 * 4 * @param p 5 * @param pieceWidth piece图片的宽 6 * @param min 向左遍历时最小的界限 7 * @return 给定Point左边的通道 8 */ 9 private List<Point> getLeftChanel(Point p, int min, int pieceWidth) 10 { 11 List<Point> result = new ArrayList<Point>(); 12 // 获取向左通道, 由一个点向左遍历, 步长为Piece图片的宽 13 for (int i = p.x - pieceWidth; i >= min 14 ; i = i - pieceWidth) 15 { 16 // 遇到障碍, 表示通道已经到尽头, 直接返回 17 if (hasPiece(i, p.y)) 18 { 19 return result; 20 } 21 result.add(new Point(i, p.y)); 22 } 23 return result; 24 } 25 26 /** 27 * 给一个Point对象, 返回它的右边通道 28 * 29 * @param p 30 * @param pieceWidth 31 * @param max 向右时的最右界限 32 * @return 给定Point右边的通道 33 */ 34 private List<Point> getRightChanel(Point p, int max, int pieceWidth) 35 { 36 List<Point> result = new ArrayList<Point>(); 37 // 获取向右通道, 由一个点向右遍历, 步长为Piece图片的宽 38 for (int i = p.x + pieceWidth; i <= max 39 ; i = i + pieceWidth) 40 { 41 // 遇到障碍, 表示通道已经到尽头, 直接返回 42 if (hasPiece(i, p.y)) 43 { 44 return result; 45 } 46 result.add(new Point(i, p.y)); 47 } 48 return result; 49 } 50 51 /** 52 * 给一个Point对象, 返回它的上面通道 53 * 54 * @param p 55 * @param min 向上遍历时最小的界限 56 * @param pieceHeight 57 * @return 给定Point上面的通道 58 */ 59 private List<Point> getUpChanel(Point p, int min, int pieceHeight) 60 { 61 List<Point> result = new ArrayList<Point>(); 62 // 获取向上通道, 由一个点向右遍历, 步长为Piece图片的高 63 for (int i = p.y - pieceHeight; i >= min 64 ; i = i - pieceHeight) 65 { 66 // 遇到障碍, 表示通道已经到尽头, 直接返回 67 if (hasPiece(p.x, i)) 68 { 69 // 如果遇到障碍, 直接返回 70 return result; 71 } 72 result.add(new Point(p.x, i)); 73 } 74 return result; 75 } 76 77 /** 78 * 给一个Point对象, 返回它的下面通道 79 * 80 * @param p 81 * @param max 向上遍历时的最大界限 82 * @return 给定Point下面的通道 83 */ 84 private List<Point> getDownChanel(Point p, int max, int pieceHeight) 85 { 86 List<Point> result = new ArrayList<Point>(); 87 // 获取向下通道, 由一个点向右遍历, 步长为Piece图片的高 88 for (int i = p.y + pieceHeight; i <= max 89 ; i = i + pieceHeight) 90 { 91 // 遇到障碍, 表示通道已经到尽头, 直接返回 92 if (hasPiece(p.x, i)) 93 { 94 // 如果遇到障碍, 直接返回 95 return result; 96 } 97 result.add(new Point(p.x, i)); 98 } 99 return result; 100 }
(六)没有转折点的横向连接
如果两个Piece在Piece[][]数组中的第二维索引值相等,那么这两个Piece就位于同一行,如前面的link(Piece p1,Piece p2)方法中,调用isXBlock(Point p1,Point p2,int pieceWidth)判断p1、p2之间是否有障碍。
下面是isXBlock方法的代码:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 /** 2 * 判断两个y座标相同的点对象之间是否有障碍, 以p1为中心向右遍历 3 * 4 * @param p1 5 * @param p2 6 * @param pieceWidth 7 * @return 两个Piece之间有障碍返回true,否则返回false 8 */ 9 private boolean isXBlock(Point p1, Point p2, int pieceWidth) 10 { 11 if (p2.x < p1.x) 12 { 13 // 如果p2在p1左边, 调换参数位置调用本方法 14 return isXBlock(p2, p1, pieceWidth); 15 } 16 for (int i = p1.x + pieceWidth; i < p2.x; i = i + pieceWidth) 17 { 18 if (hasPiece(i, p1.y)) 19 {// 有障碍 20 return true; 21 } 22 } 23 return false; 24 }
如果两个方块位于同一行,且它们之间没有障碍,那么这两个方块就可以消除,两个方块的连接信息就是它们的中心。
(七)没有转折点的纵向连接
如果两个Piece在Piece[][]数组中的第一维索引值相等,那么这两个Piece就位于同一列,如前面的link(Piece p1,Piece p2)方法中,调用isYBlock(Point p1,Point p2,int pieceWidth)判断p1、p2之间是否有障碍。
下面是isYBlock方法的代码:src\org\crazyit\link\board\impl\GameServiceImpl.java
/** * 判断两个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; }
(八)一个转折点的连接
对于两个方块连接线上只有一个转折点的情况,程序需要先找到这个转折点。为了找到这个转折点,程序定义一个遍历两个通道并获取它们交点的方法。
代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
/** * 遍历两个通道, 获取它们的交点 * * @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的向下通道是否有交点,p1的向上通道与p2的向左通道是否有交点。
当p2位于p1的右上角时,应该计算p1的向右通道与p2的向上通道是否有交点,p1的向下通道与p2的向左通道是否有交点。
根据p1与p2具有上面两种分布情形,程序提供如下方法进行处理。
代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 /** 2 * 获取两个不在同一行或者同一列的座标点的直角连接点, 即只有一个转折点 3 * 4 * @param point1 第一个点 5 * @param point2 第二个点 6 * @return 两个不在同一行或者同一列的座标点的直角连接点 7 */ 8 private Point getCornerPoint(Point point1, Point point2, int pieceWidth, 9 int pieceHeight) 10 { 11 // 先判断这两个点的位置关系 12 // point2在point1的左上角, point2在point1的左下角 13 if (isLeftUp(point1, point2) || isLeftDown(point1, point2)) 14 { 15 // 参数换位, 重新调用本方法 16 return getCornerPoint(point2, point1, pieceWidth, pieceHeight); 17 } 18 // 获取p1向右, 向上, 向下的三个通道 19 List<Point> point1RightChanel = getRightChanel(point1, point2.x, 20 pieceWidth); 21 List<Point> point1UpChanel = getUpChanel(point1, point2.y, pieceHeight); 22 List<Point> point1DownChanel = getDownChanel(point1, point2.y, 23 pieceHeight); 24 // 获取p2向下, 向左, 向下的三个通道 25 List<Point> point2DownChanel = getDownChanel(point2, point1.y, 26 pieceHeight); 27 List<Point> point2LeftChanel = getLeftChanel(point2, point1.x, 28 pieceWidth); 29 List<Point> point2UpChanel = getUpChanel(point2, point1.y, pieceHeight); 30 if (isRightUp(point1, point2)) 31 { 32 // point2在point1的右上角 33 // 获取p1向右和p2向下的交点 34 Point linkPoint1 = getWrapPoint(point1RightChanel, point2DownChanel); 35 // 获取p1向上和p2向左的交点 36 Point linkPoint2 = getWrapPoint(point1UpChanel, point2LeftChanel); 37 // 返回其中一个交点, 如果没有交点, 则返回null 38 return (linkPoint1 == null) ? linkPoint2 : linkPoint1; 39 } 40 if (isRightDown(point1, point2)) 41 { 42 // point2在point1的右下角 43 // 获取p1向下和p2向左的交点 44 Point linkPoint1 = getWrapPoint(point1DownChanel, point2LeftChanel); 45 // 获取p1向右和p2向下的交点 46 Point linkPoint2 = getWrapPoint(point1RightChanel, point2UpChanel); 47 return (linkPoint1 == null) ? linkPoint2 : linkPoint1; 48 } 49 return null; 50 }
上面代码分别处理了p2位于p1的右上、右下的两种情形。
在上面程序中用到isLeftUp、isLeftDown、isRightUp、isRightDown四个方法来判断p2位于p1的左上、左下、右上、右下4种情形。这4个方法的实现,只要对它们的X、Y坐标进行简单判断即可。
4个方法的代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 /** 2 * 判断point2是否在point1的左上角 3 * 4 * @param point1 5 * @param point2 6 * @return p2位于p1的左上角时返回true,否则返回false 7 */ 8 private boolean isLeftUp(Point point1, Point point2) 9 { 10 return (point2.x < point1.x && point2.y < point1.y); 11 } 12 13 /** 14 * 判断point2是否在point1的左下角 15 * 16 * @param point1 17 * @param point2 18 * @return p2位于p1的左下角时返回true,否则返回false 19 */ 20 private boolean isLeftDown(Point point1, Point point2) 21 { 22 return (point2.x < point1.x && point2.y > point1.y); 23 } 24 25 /** 26 * 判断point2是否在point1的右上角 27 * 28 * @param point1 29 * @param point2 30 * @return p2位于p1的右上角时返回true,否则返回false 31 */ 32 private boolean isRightUp(Point point1, Point point2) 33 { 34 return (point2.x > point1.x && point2.y < point1.y); 35 } 36 37 /** 38 * 判断point2是否在point1的右下角 39 * 40 * @param point1 41 * @param point2 42 * @return p2位于p1的右下角时返回true,否则返回false 43 */ 44 private boolean isRightDown(Point point1, Point point2) 45 { 46 return (point2.x > point1.x && point2.y > point1.y); 47 }
(九)两个转折点的连接
两个转折点的1连接又是最复杂的一种连接情况,因为两个转折点又可分为如下几种情况。
·p1、p2位于同一行,不能直接相连,就必须有两个转折点,分向上与向下两种连接情况。
·p1、p2位于同一列,不能直接相连,也必须有两个转折点,分向左与向右两种连接情况。
·p2在p1的右下角,有6种转折情况。
·p2在p1的右上角,有6种转折情况。
对于上面4种情况,同样需要分别进行处理。
1.同一行不能直接相连
p1、p2位于同一行,但它们不能直接相连,因此必须有两个转折点。当p1与p2位于同一行不能直接相连时,这两个点既可在上面相连,也可在下面相连。这两种情况都代表它们可以相连,我们先把这两种情况都加入结果中,最后再去计算最近的距离。
实现时可以先构建一个Map,Map的key为第一个转折点,Map的value为第二转折点,如果Map的size()大于1,说明这两个Point有多种连接途径,那么程序还需要计算路径最小的连接方式。
2.同一列不能直接相连
p1、p2位于同一列,但它们不能直接相连,因此必须有两个转折点。当p1与p2位于同一列不能直接相连时,这两个点既可在左边相连,也可在右边相连。这两种情况都代表它们可以相连,我们先把这两种情况都加入结果中,最后再去计算最近的距离。
同样的,我们实现时也是构建一个Map。当size()大于1,还要计算最小的连接方式。
3.p2位于p1右下角的六种转折情况
有一条垂直通道与p1的向右通道和p2的向左通道相交的方式。有一条水平通道与p1的向下通道和p2的向上通道相交的方式。即可在上面相连,也可在下面相连。即可在左边相连,也可在右边相连。共6种相连情况。
4.p2位于p1右上角的六种转折情况
与3类似,不再叙述。
对具有两个连接点的情况进行处理的代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 /** 2 * 获取两个转折点的情况 3 * 4 * @param point1 5 * @param point2 6 * @return Map对象的每个key-value对代表一种连接方式, 7 * 其中key、value分别代表第1个、第2个连接点 8 */ 9 private Map<Point, Point> getLinkPoints(Point point1, Point point2, 10 int pieceWidth, int pieceHeight) 11 { 12 Map<Point, Point> result = new HashMap<Point, Point>(); 13 // 获取以point1为中心的向上, 向右, 向下的通道 14 List<Point> p1UpChanel = getUpChanel(point1, point2.y, pieceHeight); 15 List<Point> p1RightChanel = getRightChanel(point1, point2.x, pieceWidth); 16 List<Point> p1DownChanel = getDownChanel(point1, point2.y, pieceHeight); 17 // 获取以point2为中心的向下, 向左, 向上的通道 18 List<Point> p2DownChanel = getDownChanel(point2, point1.y, pieceHeight); 19 List<Point> p2LeftChanel = getLeftChanel(point2, point1.x, pieceWidth); 20 List<Point> p2UpChanel = getUpChanel(point2, point1.y, pieceHeight); 21 // 获取Board的最大高度 22 int heightMax = (this.config.getYSize() + 1) * pieceHeight 23 + this.config.getBeginImageY(); 24 // 获取Board的最大宽度 25 int widthMax = (this.config.getXSize() + 1) * pieceWidth 26 + this.config.getBeginImageX(); 27 // 先确定两个点的关系 28 // point2在point1的左上角或者左下角 29 if (isLeftUp(point1, point2) || isLeftDown(point1, point2)) 30 { 31 // 参数换位, 调用本方法 32 return getLinkPoints(point2, point1, pieceWidth, pieceHeight); 33 } 34 // p1、p2位于同一行不能直接相连 35 if (point1.y == point2.y) 36 { 37 // 在同一行 38 // 向上遍历 39 // 以p1的中心点向上遍历获取点集合 40 p1UpChanel = getUpChanel(point1, 0, pieceHeight); 41 // 以p2的中心点向上遍历获取点集合 42 p2UpChanel = getUpChanel(point2, 0, pieceHeight); 43 Map<Point, Point> upLinkPoints = getXLinkPoints(p1UpChanel, 44 p2UpChanel, pieceHeight); 45 // 向下遍历, 不超过Board(有方块的地方)的边框 46 // 以p1中心点向下遍历获取点集合 47 p1DownChanel = getDownChanel(point1, heightMax, pieceHeight); 48 // 以p2中心点向下遍历获取点集合 49 p2DownChanel = getDownChanel(point2, heightMax, pieceHeight); 50 Map<Point, Point> downLinkPoints = getXLinkPoints(p1DownChanel, 51 p2DownChanel, pieceHeight); 52 result.putAll(upLinkPoints); 53 result.putAll(downLinkPoints); 54 } 55 // p1、p2位于同一列不能直接相连 56 if (point1.x == point2.x) 57 { 58 // 在同一列 59 // 向左遍历 60 // 以p1的中心点向左遍历获取点集合 61 List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth); 62 // 以p2的中心点向左遍历获取点集合 63 p2LeftChanel = getLeftChanel(point2, 0, pieceWidth); 64 Map<Point, Point> leftLinkPoints = getYLinkPoints(p1LeftChanel, 65 p2LeftChanel, pieceWidth); 66 // 向右遍历, 不得超过Board的边框(有方块的地方) 67 // 以p1的中心点向右遍历获取点集合 68 p1RightChanel = getRightChanel(point1, widthMax, pieceWidth); 69 // 以p2的中心点向右遍历获取点集合 70 List<Point> p2RightChanel = getRightChanel(point2, widthMax, 71 pieceWidth); 72 Map<Point, Point> rightLinkPoints = getYLinkPoints(p1RightChanel, 73 p2RightChanel, pieceWidth); 74 result.putAll(leftLinkPoints); 75 result.putAll(rightLinkPoints); 76 } 77 // point2位于point1的右上角 78 if (isRightUp(point1, point2)) 79 { 80 // 获取point1向上遍历, point2向下遍历时横向可以连接的点 81 Map<Point, Point> upDownLinkPoints = getXLinkPoints(p1UpChanel, 82 p2DownChanel, pieceWidth); 83 // 获取point1向右遍历, point2向左遍历时纵向可以连接的点 84 Map<Point, Point> rightLeftLinkPoints = getYLinkPoints( 85 p1RightChanel, p2LeftChanel, pieceHeight); 86 // 获取以p1为中心的向上通道 87 p1UpChanel = getUpChanel(point1, 0, pieceHeight); 88 // 获取以p2为中心的向上通道 89 p2UpChanel = getUpChanel(point2, 0, pieceHeight); 90 // 获取point1向上遍历, point2向上遍历时横向可以连接的点 91 Map<Point, Point> upUpLinkPoints = getXLinkPoints(p1UpChanel, 92 p2UpChanel, pieceWidth); 93 // 获取以p1为中心的向下通道 94 p1DownChanel = getDownChanel(point1, heightMax, pieceHeight); 95 // 获取以p2为中心的向下通道 96 p2DownChanel = getDownChanel(point2, heightMax, pieceHeight); 97 // 获取point1向下遍历, point2向下遍历时横向可以连接的点 98 Map<Point, Point> downDownLinkPoints = getXLinkPoints(p1DownChanel, 99 p2DownChanel, pieceWidth); 100 // 获取以p1为中心的向右通道 101 p1RightChanel = getRightChanel(point1, widthMax, pieceWidth); 102 // 获取以p2为中心的向右通道 103 List<Point> p2RightChanel = getRightChanel(point2, widthMax, 104 pieceWidth); 105 // 获取point1向右遍历, point2向右遍历时纵向可以连接的点 106 Map<Point, Point> rightRightLinkPoints = getYLinkPoints( 107 p1RightChanel, p2RightChanel, pieceHeight); 108 // 获取以p1为中心的向左通道 109 List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth); 110 // 获取以p2为中心的向左通道 111 p2LeftChanel = getLeftChanel(point2, 0, pieceWidth); 112 // 获取point1向左遍历, point2向右遍历时纵向可以连接的点 113 Map<Point, Point> leftLeftLinkPoints = getYLinkPoints(p1LeftChanel, 114 p2LeftChanel, pieceHeight); 115 result.putAll(upDownLinkPoints); 116 result.putAll(rightLeftLinkPoints); 117 result.putAll(upUpLinkPoints); 118 result.putAll(downDownLinkPoints); 119 result.putAll(rightRightLinkPoints); 120 result.putAll(leftLeftLinkPoints); 121 } 122 // point2位于point1的右下角 123 if (isRightDown(point1, point2)) 124 { 125 // 获取point1向下遍历, point2向上遍历时横向可连接的点 126 Map<Point, Point> downUpLinkPoints = getXLinkPoints(p1DownChanel, 127 p2UpChanel, pieceWidth); 128 // 获取point1向右遍历, point2向左遍历时纵向可连接的点 129 Map<Point, Point> rightLeftLinkPoints = getYLinkPoints( 130 p1RightChanel, p2LeftChanel, pieceHeight); 131 // 获取以p1为中心的向上通道 132 p1UpChanel = getUpChanel(point1, 0, pieceHeight); 133 // 获取以p2为中心的向上通道 134 p2UpChanel = getUpChanel(point2, 0, pieceHeight); 135 // 获取point1向上遍历, point2向上遍历时横向可连接的点 136 Map<Point, Point> upUpLinkPoints = getXLinkPoints(p1UpChanel, 137 p2UpChanel, pieceWidth); 138 // 获取以p1为中心的向下通道 139 p1DownChanel = getDownChanel(point1, heightMax, pieceHeight); 140 // 获取以p2为中心的向下通道 141 p2DownChanel = getDownChanel(point2, heightMax, pieceHeight); 142 // 获取point1向下遍历, point2向下遍历时横向可连接的点 143 Map<Point, Point> downDownLinkPoints = getXLinkPoints(p1DownChanel, 144 p2DownChanel, pieceWidth); 145 // 获取以p1为中心的向左通道 146 List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth); 147 // 获取以p2为中心的向左通道 148 p2LeftChanel = getLeftChanel(point2, 0, pieceWidth); 149 // 获取point1向左遍历, point2向左遍历时纵向可连接的点 150 Map<Point, Point> leftLeftLinkPoints = getYLinkPoints(p1LeftChanel, 151 p2LeftChanel, pieceHeight); 152 // 获取以p1为中心的向右通道 153 p1RightChanel = getRightChanel(point1, widthMax, pieceWidth); 154 // 获取以p2为中心的向右通道 155 List<Point> p2RightChanel = getRightChanel(point2, widthMax, 156 pieceWidth); 157 // 获取point1向右遍历, point2向右遍历时纵向可以连接的点 158 Map<Point, Point> rightRightLinkPoints = getYLinkPoints( 159 p1RightChanel, p2RightChanel, pieceHeight); 160 result.putAll(downUpLinkPoints); 161 result.putAll(rightLeftLinkPoints); 162 result.putAll(upUpLinkPoints); 163 result.putAll(downDownLinkPoints); 164 result.putAll(leftLeftLinkPoints); 165 result.putAll(rightRightLinkPoints); 166 } 167 return result; 168 }
上面代码调用了getYLinkPoints、getXLinkPoints方法来收集各种可能出现的连接路径。
getYLinkPoints、getXLinkPoints两种方法的代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
/** * 遍历两个集合, 先判断第一个集合的元素的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; }
经过上面的处理,getLinkPoints(Point point1,Point point2,int pieceWidth,int pieceHeight)方法可以找出point1、point2两个点之间的所有可能的连接情况,该方法返回一个Map对象,每个key-value对代表一种连接情况,其中key代表第一个连接点,value代表第二个连接点。
但point1、point2之间有多种连接情况时,程序还需要找出所有连接情况中的最短路径,,上面代码中调用了getShortcut(Point p1,Point p2,turns,getDistance(Point p1,Point p2))方法进行处理。
(十)找出最短距离
为了找出最短路径,程序可分为两步。
1.遍历转折点Map中所有key-value对,与原来选择的两个点构成了一个LinkInfo。每个LinkInfo代表一条完整的连接路径,并将这些LinkInfo收集成一个List集合。
2.遍历第一步得到的List<LinkInfo>集合,计算每个LinkInfo中连接全部连接点的总距离,选与最短距离相差最小的LinkInfo返回即可。
代码如下:src\org\crazyit\link\board\impl\GameServiceImpl.java
1 /** 2 * 获取p1和p2之间最短的连接信息 3 * 4 * @param p1 5 * @param p2 6 * @param turns 放转折点的map 7 * @param shortDistance 两点之间的最短距离 8 * @return p1和p2之间最短的连接信息 9 */ 10 private LinkInfo getShortcut(Point p1, Point p2, Map<Point, Point> turns, 11 int shortDistance) 12 { 13 List<LinkInfo> infos = new ArrayList<LinkInfo>(); 14 // 遍历结果Map, 15 for (Point point1 : turns.keySet()) 16 { 17 Point point2 = turns.get(point1); 18 // 将转折点与选择点封装成LinkInfo对象, 放到List集合中 19 infos.add(new LinkInfo(p1, point1, point2, p2)); 20 } 21 return getShortcut(infos, shortDistance); 22 } 23 24 /** 25 * 从infos中获取连接线最短的那个LinkInfo对象 26 * 27 * @param infos 28 * @return 连接线最短的那个LinkInfo对象 29 */ 30 private LinkInfo getShortcut(List<LinkInfo> infos, int shortDistance) 31 { 32 int temp1 = 0; 33 LinkInfo result = null; 34 for (int i = 0; i < infos.size(); i++) 35 { 36 LinkInfo info = infos.get(i); 37 // 计算出几个点的总距离 38 int distance = countAll(info.getLinkPoints()); 39 // 将循环第一个的差距用temp1保存 40 if (i == 0) 41 { 42 temp1 = distance - shortDistance; 43 result = info; 44 } 45 // 如果下一次循环的值比temp1的还小, 则用当前的值作为temp1 46 if (distance - shortDistance < temp1) 47 { 48 temp1 = distance - shortDistance; 49 result = info; 50 } 51 } 52 return result; 53 } 54 55 /** 56 * 计算List<Point>中所有点的距离总和 57 * 58 * @param points 需要计算的连接点 59 * @return 所有点的距离的总和 60 */ 61 private int countAll(List<Point> points) 62 { 63 int result = 0; 64 for (int i = 0; i < points.size() - 1; i++) 65 { 66 // 获取第i个点 67 Point point1 = points.get(i); 68 // 获取第i + 1个点 69 Point point2 = points.get(i + 1); 70 // 计算第i个点与第i + 1个点的距离,并添加到总距离中 71 result += getDistance(point1, point2); 72 } 73 return result; 74 } 75 76 /** 77 * 获取两个LinkPoint之间的最短距离 78 * 79 * @param p1 第一个点 80 * @param p2 第二个点 81 * @return 两个点的距离距离总和 82 */ 83 private int getDistance(Point p1, Point p2) 84 { 85 int xDistance = Math.abs(p1.x - p2.x); 86 int yDistance = Math.abs(p1.y - p2.y); 87 return xDistance + yDistance; 88 }
到这,连连看游戏中两个方块可能相连的所有情况都处理完成了,应用程序即可调用GameServiceImpl所提供的Link(Piece p1,Piece p2)方法来判断两个方块是否可以相连了,这个过程也是最繁琐的地方。
通过连连看游戏的分析与学习,加强了开发者界面分析与数据建模的能力。通过自定义View来实现游戏的主界面。连连看中需要判断两个方块是否可以相连,需要开发这对两个方块的位置分门别类地进行处理,也加强开发者冷静、条理化的思维。
具体实现步骤连接:
android开发学习之路——连连看之游戏Activity(四)
标签:通道 info 学习 point java bsp nbsp code map对象
原文地址:http://www.cnblogs.com/weilongfu/p/7388081.html