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

Posted Ling先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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