zoukankan      html  css  js  c++  java
  • 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组件定义如下接口。

        接口代码如下:srcorgcrazyitlinkoardGameService

     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个方法的代码如下:srcorgcrazyitlinkoardimplGameServiceImpl

     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坐标获取对应方块得代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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)方法的代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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)方法把这四种情况分开进行处理。

         代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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个方法的代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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方法的代码:srcorgcrazyitlinkoardimplGameServiceImpl.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方法的代码:srcorgcrazyitlinkoardimplGameServiceImpl.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;
        }

    (八)一个转折点的连接

        对于两个方块连接线上只有一个转折点的情况,程序需要先找到这个转折点。为了找到这个转折点,程序定义一个遍历两个通道并获取它们交点的方法。

        代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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具有上面两种分布情形,程序提供如下方法进行处理。

        代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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个方法的代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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类似,不再叙述。

        对具有两个连接点的情况进行处理的代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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两种方法的代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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返回即可。

        代码如下:srcorgcrazyitlinkoardimplGameServiceImpl.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开发学习之路——连连看之游戏界面(一)

    android开发学习之路——连连看之数据模型(二)

    android开发学习之路——连连看之加载图片(三)

    android开发学习之路——连连看之游戏Activity(四)

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

  • 相关阅读:
    fn project 试用之后的几个问题的解答
    fn project 扩展
    fn project 生产环境使用
    fn project 对象模型
    fn project AWS Lambda 格式 functions
    fn project 打包Function
    fn project Function files 说明
    fn project hot functions 说明
    fn project k8s 集成
    fn project 私有镜像发布
  • 原文地址:https://www.cnblogs.com/weilongfu/p/7388081.html
Copyright © 2011-2022 走看看