码迷,mamicode.com
首页 > 移动开发 > 详细

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

时间:2017-08-19 18:28:55      阅读:420      评论:0      收藏:0      [点我收藏+]

标签:通道   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开发学习之路——连连看之游戏界面(一)

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

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

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

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

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

标签:通道   info   学习   point   java   bsp   nbsp   code   map对象   

原文地址:http://www.cnblogs.com/weilongfu/p/7388081.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!