android开发学习之路——连连看之游戏逻辑
Posted Ling先生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android开发学习之路——连连看之游戏逻辑相关的知识,希望对你有一定的参考价值。
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
/** * 遍历两个通道, 获取它们的交点 * * 以上是关于android开发学习之路——连连看之游戏逻辑的主要内容,如果未能解决你的问题,请参考以下文章Android Studio实现连连看小游戏,比比看谁过关最快~