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

/**
     * 遍歷兩個通道, 獲取它們的交點
     * 
     * @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開發學習之路——連連看之游戲邏輯(五)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM