android开发学习之路——连连看之游戏逻辑

风流不在谈锋胜,袖手无言味最长。这篇文章主要讲述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*/ 6void start(); 7 8/** 9* 定义一个接口方法, 用于返回一个二维数组 10* 11* @return 存放方块对象的二维数组 12*/ 13Piece[][] getPieces(); 14 15/** 16* 判断参数Piece[][]数组中是否还存在非空的Piece对象 17* 18* @return 如果还剩Piece对象返回true, 没有返回false 19*/ 20boolean hasPieces(); 21 22/** 23* 根据鼠标的x座标和y座标, 查找出一个Piece对象 24* 25* @param touchX 鼠标点击的x座标 26* @param touchY 鼠标点击的y座标 27* @return 返回对应的Piece对象, 没有返回null 28*/ 29Piece 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*/ 38LinkInfo 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方法 4private Piece[][] pieces; 5// 游戏配置对象 6private GameConf config; 7 8public GameServiceImpl(GameConf config) 9{ 10// 将游戏的配置对象设置本类中 11this.config = config; 12} 13 14@Override 15public void start() 16{ 17// 定义一个AbstractBoard对象 18AbstractBoard board = null; 19Random random = new Random(); 20// 获取一个随机数, 可取值0、1、2、3四值。 21int index = random.nextInt(4); 22// 随机生成AbstractBoard的子类实例 23switch (index) 24{ 25case 0: 26// 0返回VerticalBoard(竖向) 27board = new VerticalBoard(); 28break; 29case 1: 30// 1返回HorizontalBoard(横向) 31board = new HorizontalBoard(); 32break; 33default: 34// 默认返回FullBoard 35board = new FullBoard(); 36break; 37} 38// 初始化Piece[][]数组 39this.pieces = board.create(config); 40} 41 42// 直接返回本对象的Piece[][]数组 43@Override 44public Piece[][] getPieces() 45{ 46return this.pieces; 47} 48 49// 实现接口的hasPieces方法 50@Override 51public boolean hasPieces() 52{ 53// 遍历Piece[][]数组的每个元素 54for (int i = 0; i < pieces.length; i++) 55{ 56for (int j = 0; j < pieces[i].length; j++) 57{ 58// 只要任意一个数组元素不为null,也就是还剩有非空的Piece对象 59if (pieces[i][j] != null) 60{ 61return true; 62} 63} 64} 65return 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 3public Piece findPiece(float touchX, float touchY) 4{ 5// 由于在创建Piece对象的时候, 将每个Piece的开始座标加了 6// GameConf中设置的beginImageX/beginImageY值, 因此这里要减去这个值 7int relativeX = (int) touchX - this.config.getBeginImageX(); 8int relativeY = (int) touchY - this.config.getBeginImageY(); 9// 如果鼠标点击的地方比board中第一张图片的开始x座标和开始y座标要小, 即没有找到相应的方块 10if (relativeX < 0 || relativeY < 0) 11{ 12return null; 13} 14// 获取relativeX座标在Piece[][]数组中的第一维的索引值 15// 第二个参数为每张图片的宽 16int indexX = getIndex(relativeX, GameConf.PIECE_WIDTH); 17// 获取relativeY座标在Piece[][]数组中的第二维的索引值 18// 第二个参数为每张图片的高 19int indexY = getIndex(relativeY, GameConf.PIECE_HEIGHT); 20// 这两个索引比数组的最小索引还小, 返回null 21if (indexX < 0 || indexY < 0) 22{ 23return null; 24} 25// 这两个索引比数组的最大索引还大(或者等于), 返回null 26if (indexX > = this.config.getXSize() 27|| indexY > = this.config.getYSize()) 28{ 29return null; 30} 31// 返回Piece[][]数组的指定元素 32return 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为每张图片边的长或者宽 3private int getIndex(int relative, int size) 4{ 5// 表示座标relative不在该数组中 6int index = -1; 7// 让座标除以边长, 没有余数, 索引减1 8// 例如点了x座标为20, 边宽为10, 20 % 10 没有余数, 9// index为1, 即在数组中的索引为1(第二个元素) 10if (relative % size == 0) 11{ 12index = relative / size - 1; 13} 14else 15{ 16// 有余数, 例如点了x座标为21, 边宽为10, 21 % 10有余数, index为2 17// 即在数组中的索引为2(第三个元素) 18index = relative / size; 19} 20return index; 21}

 
(四)判断两个方块是否可以相连
      判断两个方块是否可以相连是本程序需要处理的最繁琐的地方:两个方块可以相连的情形比较多,大致可分为:
    ·两个方块位于同一条水平线,可以直接相连。
    ·两个方块位于同一条竖直线,可以直接相连。
    ·两个方块以两条线段相连,有1个拐点。
    ·两个方块以三条线段相连,有2个拐点。
    下面link(Piece p1,Piece p2)方法把这四种情况分开进行处理。
      代码如下:src\\org\\crazyit\\link\\board\\impl\\GameServiceImpl.java

1 // 实现接口的link方法 2@Override 3public LinkInfo link(Piece p1, Piece p2) 4{ 5// 两个Piece是同一个, 即选中了同一个方块, 返回null 6if (p1.equals(p2)) 7return null; 8// 如果p1的图片与p2的图片不相同, 则返回null 9if (!p1.isSameImage(p2)) 10return null; 11// 如果p2在p1的左边, 则需要重新执行本方法, 两个参数互换 12if (p2.getIndexX() < p1.getIndexX()) 13return link(p2, p1); 14// 获取p1的中心点 15Point p1Point = p1.getCenter(); 16// 获取p2的中心点 17Point p2Point = p2.getCenter(); 18// 如果两个Piece在同一行 19if (p1.getIndexY() == p2.getIndexY()) 20{ 21// 它们在同一行并可以相连 22if (!isXBlock(p1Point, p2Point, GameConf.PIECE_WIDTH)) 23{ 24return new LinkInfo(p1Point, p2Point); 25} 26} 27// 如果两个Piece在同一列 28if (p1.getIndexX() == p2.getIndexX()) 29{ 30if (!isYBlock(p1Point, p2Point, GameConf.PIECE_HEIGHT)) 31{ 32// 它们之间没有真接障碍, 没有转折点 33return new LinkInfo(p1Point, p2Point); 34} 35} 36// 有一个转折点的情况 37// 获取两个点的直角相连的点, 即只有一个转折点 38Point cornerPoint = getCornerPoint(p1Point, p2Point, 39GameConf.PIECE_WIDTH, GameConf.PIECE_HEIGHT); 40if (cornerPoint != null) 41{ 42return new LinkInfo(p1Point, cornerPoint, p2Point); 43} 44// 该map的key存放第一个转折点, value存放第二个转折点, 45// map的size()说明有多少种可以连的方式 46Map< Point, Point> turns = getLinkPoints(p1Point, p2Point, 47GameConf.PIECE_WIDTH, GameConf.PIECE_WIDTH); 48if (turns.size() != 0) 49{ 50return getShortcut(p1Point, p2Point, turns, 51getDistance(p1Point, p2Point)); 52} 53return 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*/ 9private List< Point> getLeftChanel(Point p, int min, int pieceWidth) 10{ 11List< Point> result = new ArrayList< Point> (); 12// 获取向左通道, 由一个点向左遍历, 步长为Piece图片的宽 13for (int i = p.x - pieceWidth; i > = min 14; i = i - pieceWidth) 15{ 16// 遇到障碍, 表示通道已经到尽头, 直接返回 17if (hasPiece(i, p.y)) 18{ 19return result; 20} 21result.add(new Point(i, p.y)); 22} 23return result; 24} 25 26/** 27* 给一个Point对象, 返回它的右边通道 28* 29* @param p 30* @param pieceWidth 31* @param max 向右时的最右界限 32* @return 给定Point右边的通道 33*/ 34private List< Point> getRightChanel(Point p, int max, int pieceWidth) 35{ 36List< Point> result = new ArrayList< Point> (); 37// 获取向右通道, 由一个点向右遍历, 步长为Piece图片的宽 38for (int i = p.x + pieceWidth; i < = max 39; i = i + pieceWidth) 40{ 41// 遇到障碍, 表示通道已经到尽头, 直接返回 42if (hasPiece(i, p.y)) 43{ 44return result; 45} 46result.add(new Point(i, p.y)); 47} 48return result; 49} 50 51/** 52* 给一个Point对象, 返回它的上面通道 53* 54* @param p 55* @param min 向上遍历时最小的界限 56* @param pieceHeight 57* @return 给定Point上面的通道 58*/ 59private List< Point> getUpChanel(Point p, int min, int pieceHeight) 60{ 61List< Point> result = new ArrayList< Point> (); 62// 获取向上通道, 由一个点向右遍历, 步长为Piece图片的高 63for (int i = p.y - pieceHeight; i > = min 64; i = i - pieceHeight) 65{ 66// 遇到障碍, 表示通道已经到尽头, 直接返回 67if (hasPiece(p.x, i)) 68{ 69// 如果遇到障碍, 直接返回 70return result; 71} 72result.add(new Point(p.x, i)); 73} 74return result; 75} 76 77/** 78* 给一个Point对象, 返回它的下面通道 79* 80* @param p 81* @param max 向上遍历时的最大界限 82* @return 给定Point下面的通道 83*/ 84private List< Point> getDownChanel(Point p, int max, int pieceHeight) 85{ 86List< Point> result = new ArrayList< Point> (); 87// 获取向下通道, 由一个点向右遍历, 步长为Piece图片的高 88for (int i = p.y + pieceHeight; i < = max 89; i = i + pieceHeight) 90{ 91// 遇到障碍, 表示通道已经到尽头, 直接返回 92if (hasPiece(p.x, i)) 93{ 94// 如果遇到障碍, 直接返回 95return result; 96} 97result.add(new Point(p.x, i)); 98} 99return 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*/ 9private boolean isXBlock(Point p1, Point p2, int pieceWidth) 10{ 11if (p2.x < p1.x) 12{ 13// 如果p2在p1左边, 调换参数位置调用本方法 14return isXBlock(p2, p1, pieceWidth); 15} 16for (int i = p1.x + pieceWidth; i < p2.x; i = i + pieceWidth) 17{ 18if (hasPiece(i, p1.y)) 19{// 有障碍 20return true; 21} 22} 23return 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; }

 
(八)一个转折点的连接
    对于两个方块连接线上只有一个转折点的情况,程序需要先找到这个转折点。为了找到这个转折点,程序定义一个遍历两个通道并获取它们交点的方法。
【android开发学习之路——连连看之游戏逻辑】    代码如下:src\\org\\crazyit\\link\\board\\impl\\GameServiceImpl.java
/** * 遍历两个通道, 获取它们的交点 * *

    推荐阅读