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來實現游戲的主界面。連連看中需要判斷兩個方塊是否可以相連,需要開發這對兩個方塊的位置分門別類地進行處理,也加強開發者冷靜、條理化的思維。
具體實現步驟連接: