疯狂连连看游戏开发

游戏简介:疯狂连连看,是一款简单易玩的手机休闲游戏,界面布局简单,玩法简单,适合广大年龄层的用户进行休闲、放松。该游戏,应用于Android手机操作系统,Android1.6以上。

游戏开发过程总结说明

==>>>游戏开发环境

操作系统:Win7 32位操作系统

处理器:Intel(R) Pentium(R)CPU 海尔P6000笔记本电脑

内存:2GB

==>>>游戏开发工具、软件

Eclipse软件,插件ADT,SDK Android 2.3.3版本

==>>>游戏功能

可以随机切换不同方向的连连看排列,增加可玩性,时间在100s内,在规定实践内消除所有方块,就可以赢得游戏,在规定时间没能消除所有方块,游戏失败。

==>>>游戏运行环境

Android手机操作系统,本人的是Motorala MB508,操作系统版本是Android 2.3.7 最稳定的一个版本

==>>>项目说明

项目名称:Link

项目大小:1.40MB

项目开发思维导图 凸^-^凸 本人刚接触思维导图,简单做了这个游戏的思维导图

==>>>项目开发5大块

第一part:开发游戏界面

第二part:连连看的数据模型

第三part:加载界面的图片

第四part:实现游戏Activity

第五part:实现游戏逻辑

第一part思维导图截图:

第二part思维导图截图:

第三part思维导图截图:

第四part思维导图截图:

第五part思维导图截图:

==>>>项目代码说明

第一块:开发游戏界面

1.开发界面布局

布局代码:main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" android:background="@drawable/room"><!-- 游戏主界面的自定义组件 --><org.wwj.link.view.GameViewandroid:id="@+id/gameView"android:layout_width="fill_parent"android:layout_height="fill_parent"/><!-- 水平排列的LinearLayout --><LinearLayout android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"android:layout_marginTop="380px"android:background="#1e72bb"android:gravity="center"><!-- 控制游戏开始的按钮 --><Buttonandroid:id="@+id/startButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/button_selector"/><!-- 显示游戏剩余时间的文本框 --><TextView android:id="@+id/timeText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:textSize="20dip"android:width="150px"android:textColor="#ff9"/>"</LinearLayout>
</RelativeLayout>

指定按钮的背景色使用@drawable/button_selector

==>>button_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" ><!-- 指定按钮按下时的图片 --><item android:state_pressed="true"android:drawable="@drawable/start_down"/><!-- 指定按钮松开的图片 --><item android:state_pressed="false"android:drawable="@drawable/start"/>
</selector>

效果图:

2.开发游戏界面组件

上面的布局文件用到了自顶义的View:GameView,它从View基类派生而出,这个自定义View的功能就是游戏状态来绘制游戏界面上的全部方块。

在实现GameView之前,要为它提供一个Piece类,一个Piece对象代表游戏界面上的一个方块。

==>>Piece.java

package org.wwj.link.view;import android.graphics.Point;//开发Piece类,一个Piece对象代表一个方块
public class Piece
{// 保存方块对象的所对应的图片private PieceImage image;// 该方块的左上角的x坐标private int beginX;// 该方块的左上角的y座标private int beginY;// 该对象在Piece[][]数组中第一维的索引值private int indexX;// 该对象在Piece[][]数组中第二维的索引值private int indexY;// 只设置该Piece对象在数组中的索引值public Piece(int indexX , int indexY){this.indexX = indexX;this.indexY = indexY;}//获得该方块的左上角的x坐标public int getBeginX(){return beginX;}//设置方块的左上角的x坐标public void setBeginX(int beginX){this.beginX = beginX;}//获取该方块的做上角的y坐标public int getBeginY(){return beginY;}//设置方块的左上角的y坐标public void setBeginY(int beginY){this.beginY = beginY;}//获取方块的第一维的索引值public int getIndexX(){return indexX;}//设置方块的第一维的索引值public void setIndexX(int indexX){this.indexX = indexX;}//获取方块的第二维的索引值public int getIndexY(){return indexY;}//设置方块的第二维的索引值public void setIndexY(int indexY){this.indexY = indexY;}//获取图片对象public PieceImage getImage(){return image;}//设置图片对象public void setImage(PieceImage image){this.image = image;}// 获取该Piece的中心public Point getCenter(){ //Piece对象的中心是该图片的宽高+该图片对应的左上角的x、y坐标return new Point(getImage().getImage().getWidth() / 2+ getBeginX(), getBeginY()+ getImage().getImage().getHeight() / 2);} // 判断两个Piece上的图片是否相同public boolean isSameImage(Piece other){if (image == null){if (other.image != null)return false;}// 只要Piece封装图片ID相同,即可认为两个Piece相等。return image.getImageId() == other.image.getImageId();}
}

上面的Piece类中封装的PieceImage代表了该方块上的图片,PieceImage封装了两个信息:

>> Bitmap对象

>> 图片资源的ID

==>>PieceImage.java

package org.wwj.link.view;import android.graphics.Bitmap;/** 使用PieceImage来封装两个信息:* =>Bitmap对象* =>图片资源的ID*/
public class PieceImage
{private Bitmap image;private int imageId;// 有参数的构造器public PieceImage(Bitmap image, int imageId){super();this.image = image;this.imageId = imageId;}public Bitmap getImage(){return image;}public void setImage(Bitmap image){this.image = image;}public int getImageId(){return imageId;}public void setImageId(int imageId){this.imageId = imageId;}
}

GameView主要就是根据游戏的状态数据来绘制界面上的方块,GameView继承View组件,重写了View组件上onDraw(Canvas canvas)方法,重写该方法主要是绘制游戏里剩余的方块;除此之外,它还会负责绘制连接方块的连接线。

程序当中用到的GameService类、LinkInfo类主要在后面实现
==>>GameView.java

package org.wwj.link.view;import java.util.List;import org.wwj.link.board.GameService;
import org.wwj.link.object.LinkInfo;
import org.wwj.link.util.ImageUtil;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;public class GameView extends View
{// 游戏逻辑的实现类private GameService gameService;// 保存当前已经被选中的方块private Piece selectedPiece;// 连接信息对象private LinkInfo linkInfo;private Paint paint;// 选中标识的图片对象private Bitmap selectImage;public GameView(Context context, AttributeSet attrs){super(context, attrs);this.paint = new Paint();// 设置连接线的颜色this.paint.setColor(Color.RED);// 设置连接线的粗细this.paint.setStrokeWidth(3);// 利用ImageUtil工具类,获取选中标识的图片this.selectImage = ImageUtil.getSelectImage(context);}public void setLinkInfo(LinkInfo linkInfo){this.linkInfo = linkInfo;}public void setGameService(GameService gameService){this.gameService = gameService;}@Overrideprotected void onDraw(Canvas canvas){super.onDraw(canvas);if (this.gameService == null)return;Piece[][] pieces = gameService.getPieces();if (pieces != null){// 遍历pieces二维数组for (int i = 0; i < pieces.length; i++){for (int j = 0; j < pieces[i].length; j++){// 如果二维数组中该元素不为空(即有方块),将这个方块的图片画出来if (pieces[i][j] != null){// 得到这个Piece对象Piece piece = pieces[i][j];// 根据方块左上角X、Y座标绘制方块canvas.drawBitmap(piece.getImage().getImage(),piece.getBeginX(), piece.getBeginY(), null);}}}}// 如果当前对象中有linkInfo对象, 即连接信息if (this.linkInfo != null){// 绘制连接线drawLine(this.linkInfo, canvas);// 处理完后清空linkInfo对象this.linkInfo = null;}// 画选中标识的图片if (this.selectedPiece != null){canvas.drawBitmap(this.selectImage, this.selectedPiece.getBeginX(),this.selectedPiece.getBeginY(), null);}}// 根据LinkInfo绘制连接线的方法。private void drawLine(LinkInfo linkInfo, Canvas canvas){// 获取LinkInfo中封装的所有连接点List<Point> points = linkInfo.getLinkPoints();// 依次遍历linkInfo中的每个连接点for (int i = 0; i < points.size() - 1; i++){// 获取当前连接点与下一个连接点Point currentPoint = points.get(i);Point nextPoint = points.get(i + 1);// 绘制连线canvas.drawLine(currentPoint.x , currentPoint.y,nextPoint.x, nextPoint.y, this.paint);}}// 设置当前选中方块的方法public void setSelectedPiece(Piece piece){this.selectedPiece = piece;}// 开始游戏方法public void startGame(){this.gameService.start();//重绘this.postInvalidate();}
}

3.处理方块之间的连接线

LinkInfo是一个非常实用的工具类,它用于封装两个方块之间的连接信息--其实就是封装了一个List,List里保存了连接线需要经过的点。

它包括三个构造器,分别处理没有拐点,一个拐点,两个拐点的情况。

==>>LinkInfo.java

package org.wwj.link.object;import java.util.ArrayList;
import java.util.List;import android.graphics.Point;public class LinkInfo
{// 创建一个集合用于保存连接点private List<Point> points = new ArrayList<Point>();// 提供第一个构造器, 表示两个Point可以直接相连, 没有转折点public LinkInfo(Point p1, Point p2){// 加到集合中去points.add(p1);points.add(p2);}// 提供第二个构造器, 表示三个Point可以相连, p2是p1与p3之间的转折点public LinkInfo(Point p1, Point p2, Point p3){points.add(p1);points.add(p2);points.add(p3);}// 提供第三个构造器, 表示四个Point可以相连, p2, p3是p1与p4的转折点public LinkInfo(Point p1, Point p2, Point p3, Point p4){points.add(p1);points.add(p2);points.add(p3);points.add(p4);}// 返回连接集合public List<Point> getLinkPoints(){return points;}
}

第二块:连连看的状态数据模型

1.定义数据模型

建立数据模型是实现游戏逻辑的一个重要部分,首先我们需要把游戏的数据模型给定义出来,连连看的游戏界面是一个N x M的“网格”,每个网格上显示一张图片。

我们开发的这个连连看游戏就是一个6 x 7的网格,一共42张图片,这就是我们定义的数据模型。我们用什么来保存游戏的状态模型呢?就是之前定义的Piece类,定义一个Piece[][]二维数组来保存,因为Piece对象封装的信息更多,包含了方块的左上角的X、Y坐标,而且还包含了该Piece所显示的图片、图片ID等。

2.初始化游戏状态数据

定义一个抽象类AbstractBoard,作为一个模板,让子类去实现相应的抽象方法。

==>>AbstractBoard.java

package org.wwj.link.board;import java.util.List;import org.wwj.link.object.GameConf;
import org.wwj.link.util.ImageUtil;
import org.wwj.link.view.Piece;
import org.wwj.link.view.PieceImage;public abstract class AbstractBoard
{// 定义一个抽象方法, 让子类去实现protected abstract List<Piece> createPieces(GameConf config,Piece[][] pieces);public Piece[][] create(GameConf config){// 创建Piece[][]数组Piece[][] pieces = new Piece[config.getXSize()][config.getYSize()];// 返回非空的Piece集合, 该集合由子类去创建List<Piece> notNullPieces = createPieces(config, pieces);// 根据非空Piece对象的集合的大小来取图片List<PieceImage> playImages = ImageUtil.getPlayImages(config.getContext(),notNullPieces.size());// 所有图片的宽、高都是相同的int imageWidth = playImages.get(0).getImage().getWidth();int imageHeight = playImages.get(0).getImage().getHeight();// 遍历非空的Piece集合for (int i = 0; i < notNullPieces.size(); i++){// 依次获取每个Piece对象Piece piece = notNullPieces.get(i);piece.setImage(playImages.get(i));// 计算每个方块左上角的X、Y座标piece.setBeginX(piece.getIndexX() * imageWidth+ config.getBeginImageX());piece.setBeginY(piece.getIndexY() * imageHeight+ config.getBeginImageY());// 将该方块对象放入方块数组的相应位置处pieces[piece.getIndexX()][piece.getIndexY()] = piece;}return pieces;}
}

下面为AbstractBoard实现的3个子类

1.矩阵排列的方块

效果如图:

==>>FullBoard.java

package org.wwj.link.board.impl;import java.util.ArrayList;
import java.util.List;import org.wwj.link.board.AbstractBoard;
import org.wwj.link.object.GameConf;
import org.wwj.link.view.Piece;public class FullBoard extends AbstractBoard
{@Overrideprotected List<Piece> createPieces(GameConf config,Piece[][] pieces){// 创建一个Piece集合, 该集合里面存放初始化游戏时所需的Piece对象List<Piece> notNullPieces = new ArrayList<Piece>();for (int i = 1; i < pieces.length - 1; i++){for (int j = 1; j < pieces[i].length - 1; j++){// 先构造一个Piece对象, 只设置它在Piece[][]数组中的索引值,// 所需要的PieceImage由其父类负责设置。Piece piece = new Piece(i, j);// 添加到Piece集合中notNullPieces.add(piece);}}return notNullPieces;}
}

2.竖向排列的方块

效果图:

==>>VerticalBoard.java

package org.wwj.link.board.impl;import java.util.ArrayList;
import java.util.List;import org.wwj.link.board.AbstractBoard;
import org.wwj.link.object.GameConf;
import org.wwj.link.view.Piece;public class VerticalBoard extends AbstractBoard
{protected List<Piece> createPieces(GameConf config,Piece[][] pieces){// 创建一个Piece集合, 该集合里面存放初始化游戏时所需的Piece对象List<Piece> notNullPieces = new ArrayList<Piece>();for (int i = 0; i < pieces.length; i++){for (int j = 0; j < pieces[i].length; j++){// 加入判断, 符合一定条件才去构造Piece对象, 并加到集合中if (i % 2 == 0){// 如果x能被2整除, 即单数列不会创建方块// 先构造一个Piece对象, 只设置它在Piece[][]数组中的索引值,// 所需要的PieceImage由其父类负责设置。Piece piece = new Piece(i, j);// 添加到Piece集合中notNullPieces.add(piece);}}}return notNullPieces;}
}

3.横向排列的方块

效果图:

第三块:加载界面的图片

这也是一个工具类:ImageUtil,用于加载程序界面上所需的图片

程序的实现思路可以分为4步:

1.通过反射来获取R.drawable的所有Field(Android的每张图片资源都会自动转换为R.drawable的静态Field),并将这些Field值田间到一个List集合中。

2.从第一步得到的List集合中随机“抽取”N/2个图片ID。

3.将第二步得到的N/2个图片ID全部复制一份,这样就得到了N个图片ID,而且每个图片ID都可以找到与之配对的。

4.将第三步得到的N个图片ID再次“随机打乱”,并根据图片ID加载对应的Bitmap对象,最后把图片ID及对应的Bitmap封装成PieceImage后返回。

package org.wwj.link.util;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;import org.wwj.link.Activity.R;
import org.wwj.link.view.PieceImage;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;public class ImageUtil
{// 保存所有连连看图片资源值(int类型)private static List<Integer> imageValues = getImageValues();//获取连连看所有图片的ID(约定所有图片ID以p_开头)public static List<Integer> getImageValues(){try{// 得到R.drawable所有的属性, 即获取drawable目录下的所有图片Field[] drawableFields = R.drawable.class.getFields();List<Integer> resourceValues = new ArrayList<Integer>();for (Field field : drawableFields){// 如果该Field的名称以p_开头if (field.getName().indexOf("p_") != -1){resourceValues.add(field.getInt(R.drawable.class));}}return resourceValues;}catch (Exception e){return null;}}/*** 随机从sourceValues的集合中获取size个图片ID, 返回结果为图片ID的集合* * @param sourceValues 从中获取的集合* @param size 需要获取的个数* @return size个图片ID的集合*/public static List<Integer> getRandomValues(List<Integer> sourceValues,int size){// 创建一个随机数生成器Random random = new Random();// 创建结果集合List<Integer> result = new ArrayList<Integer>();for (int i = 0; i < size; i++){try{// 随机获取一个数字,大于、小于sourceValues.size()的数值int index = random.nextInt(sourceValues.size());// 从图片ID集合中获取该图片对象Integer image = sourceValues.get(index);// 添加到结果集中result.add(image);}catch (IndexOutOfBoundsException e){return result;}}return result;}/*** 从drawable目录中中获取size个图片资源ID(以p_为前缀的资源名称), 其中size为游戏数量* * @param size 需要获取的图片ID的数量* @return size个图片ID的集合*/public static List<Integer> getPlayValues(int size){if (size % 2 != 0){// 如果该数除2有余数,将size加1size += 1;}// 再从所有的图片值中随机获取size的一半数量List<Integer> playImageValues = getRandomValues(imageValues, size / 2);// 将playImageValues集合的元素增加一倍(保证所有图片都有与之配对的图片)playImageValues.addAll(playImageValues);// 将所有图片ID随机“洗牌”Collections.shuffle(playImageValues);return playImageValues;}/*** 将图片ID集合转换PieceImage对象集合,PieceImage封装了图片ID与图片本身* * @param context* @param resourceValues* @return size个PieceImage对象的集合*/public static List<PieceImage> getPlayImages(Context context, int size){// 获取图片ID组成的集合List<Integer> resourceValues = getPlayValues(size);List<PieceImage> result = new ArrayList<PieceImage>();// 遍历每个图片IDfor (Integer value : resourceValues){// 加载图片Bitmap bm = BitmapFactory.decodeResource(context.getResources(),  value);// 封装图片ID与图片本身PieceImage pieceImage = new PieceImage(bm, value);result.add(pieceImage);}return result;}// 获取选中标识的图片public static Bitmap getSelectImage(Context context){ //利用位图工场获取图片资源Bitmap bm = BitmapFactory.decodeResource(context.getResources(),R.drawable.selected);return bm;}
}

第四块:实现游戏逻辑

游戏逻辑是整个游戏开发过程中最复杂的部分,因为我们需要设计相应的算法来实现游戏的基本功能,如果不能对游戏有很透彻的分析,这部分将变得举步维艰。

首先定义一个业务逻辑类:GameService接口

package org.wwj.link.board;import org.wwj.link.object.LinkInfo;
import org.wwj.link.view.Piece;
//负责游戏的逻辑实现
public interface GameService {/*** 控制游戏的方法*/void start();/*** 定义一个接口方法,用于返回一个二维数组* @return 存放方块对象的二维数组*/Piece[][] getPieces();/*** 判断参数Piece[][]数组中是否还存在非空的Piece对象* @return 如果还剩下Pieces对象则返回true,没有则返回false*/boolean hasPieces();/*** 根据鼠标的x坐标和y坐标,查找出一个Piece对象* @param touchX 鼠标点击的x坐标* @param touchY 鼠标点击的y坐标* @return 返回对应的Piece对象,没有则返回null*/Piece findPiece(float touchX, float touchY);/*** 判断两个Piece是否可以相连,如果可以相连,则返回LinkInfo对象* @param p1  第一个Piece对象* @param p2 第二个Piece对象* @return 如果可以相连看,则返回LinkInfo对象,如果两个Piece不可以连接,返回null*/LinkInfo link(Piece p1, Piece p2);
}

实现GameService组件

这部分内容是整个项目开发最重要的部分,除了实现接口的方法,还需要实现游戏各种逻辑的情况,为了实现各种逻辑,还需要分治实现各种方法。

下面解析各种方法的作用:

start():初始化游戏状态,开始游戏的方法。

Piece[][] getPieces():返回游戏状态的Piece[][]数组

boolean hasPieces():判断Piece[][]数组中是否还剩Piece对象;如果所有Piece都被消除了,游戏也就胜利了。

Piece findPiece(flaot touchX, float touchY):根据触碰点的X、Y坐标来获取。

LinkInfo link(Piece p1, Piece p2):判断p1、p2两个是否可以相连。

int getIndex(int relative, int size):工具方法,根据relative坐标计算相对于Piece[][]数组的第一维//或第二维的索引值。

List<Point> getLeftChanel(Point p, int min, int pieceWidth):获取左边通道

List<Point> getRightChanel(Point p, int max, int pieceWidth): 获取右边通道

List<Point> getUpChanel(Point p, int min, int pieceHeight):获取上通道

List<Point> getDownChanel(Point p, int max, int pieceHeight):获取下通道

boolean isXBlock(Point p1, Point p2, int pieceWidth): 判断两个Y坐标相同的点对象之间是否有障碍。

boolean isYBlock(Point p1, Point p2, int pieceHeight): 判断两个X坐标相同的点对象之间是否有障碍。

Point getWrapPoint(List<Point> p1Chanel, List<Point> p2Chanel): 遍历两个通道,获取它们的交点。

Point getCornerrPoint(Point point1, Point point2, int pieceWidth, int pieceHeight): 获取两个不在同一行或者同一列的坐标点的直角连接点,即只有一个转折点。

boolean isLeftUp(Point p1,Point p2): 判断p2是否在p1的左上角。

boolean isLeftDown(Point p1, Point p2): 判断p2是否在p1的左下角。

boolean isRightUp(Point p1, Point p2): 判断p2是否p1的右上角。

boolean isRightDown(Point p1, Point p2): 判断p2是否在p1的右下角。

boolean hasPiece(int x, int y):判断GamePanel的X,Y坐标是否有Piece对象。

Map<Point,Point> getLinkPoints(Point point1, Point point2, int pieceWidth, int pieceHeight):获取两个转折点的情况。

Map<Point,Point> getYLinkPoints(List<Point> p1Chanel, List<Point> p2Chanel, int pieceHeight)

Map<Point,Point> getXLinkPoints(List<Point> p1Chanel, List<Point> p2Chanel, int pieceWidth):用来收集各种可能出现的连接路径。

LinkInfo getShortCut(Point p1, Point p2, Map<Point, Point> turns, int shortDistance):获取p1, p2之间最短的连接信息。

int countAll(List<Point> points):计算List<Point>中所有点的距离总和。

int getDistance(Point p1, Point p2):获取两个LinkPoint之间的最短距离。

package org.wwj.link.board.impl;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;import org.wwj.link.board.AbstractBoard;
import org.wwj.link.board.GameService;
import org.wwj.link.object.GameConf;
import org.wwj.link.object.LinkInfo;
import org.wwj.link.view.Piece;import android.graphics.Point;public class GameServiceImpl implements GameService
{// 定义一个Piece[][]数组,只提供getter方法private Piece[][] pieces;// 游戏配置对象private GameConf config;public GameServiceImpl(GameConf config){// 将游戏的配置对象设置本类中this.config = config;}public void start(){// 定义一个AbstractBoard对象AbstractBoard board = null;Random random = new Random();// 获取一个随机数, 可取值0、1、2、3四值。int index = random.nextInt(4);// 随机生成AbstractBoard的子类实例switch (index){case 0:// 0返回VerticalBoard(竖向)board = new VerticalBoard();break;case 1:// 1返回HorizontalBoard(横向)board = new HorizontalBoard();break;default:// 默认返回FullBoardboard = new FullBoard();break;}// 初始化Piece[][]数组this.pieces = board.create(config);}// 直接返回本对象的Piece[][]数组public Piece[][] getPieces(){return this.pieces;}// 实现接口的hasPieces方法public boolean hasPieces(){// 遍历Piece[][]数组的每个元素for (int i = 0; i < pieces.length; i++){for (int j = 0; j < pieces[i].length; j++){// 只要任意一个数组元素不为null,也就是还剩有非空的Piece对象if (pieces[i][j] != null){return true;}}}return false;}// 根据触碰点的位置查找相应的方块public Piece findPiece(float touchX, float touchY){// 由于在创建Piece对象的时候, 将每个Piece的开始座标加了// GameConf中设置的beginImageX/beginImageY值, 因此这里要减去这个值int relativeX = (int) touchX - this.config.getBeginImageX();int relativeY = (int) touchY - this.config.getBeginImageY();// 如果鼠标点击的地方比board中第一张图片的开始x座标和开始y座标要小, 即没有找到相应的方块if (relativeX < 0 || relativeY < 0){return null;}// 获取relativeX座标在Piece[][]数组中的第一维的索引值// 第二个参数为每张图片的宽40int indexX = getIndex(relativeX, GameConf.PIECE_WIDTH);// 获取relativeY座标在Piece[][]数组中的第二维的索引值// 第二个参数为每张图片的高40int indexY = getIndex(relativeY, GameConf.PIECE_HEIGHT);// 这两个索引比数组的最小索引还小, 返回nullif (indexX < 0 || indexY < 0){return null;}// 这两个索引比数组的最大索引还大(或者等于), 返回nullif (indexX >= this.config.getXSize()|| indexY >= this.config.getYSize()){return null;}// 返回Piece[][]数组的指定元素return this.pieces[indexX][indexY];}// 工具方法, 根据relative座标计算相对于Piece[][]数组的第一维// 或第二维的索引值 ,size为每张图片边的长或者宽private int getIndex(int relative, int size){// 表示座标relative不在该数组中int index = -1;// 让座标除以边长, 没有余数, 索引减1// 例如点了x座标为20, 边宽为10, 20 % 10 没有余数,// index为1, 即在数组中的索引为1(第二个元素)if (relative % size == 0){index = relative / size - 1;}else{// 有余数, 例如点了x座标为21, 边宽为10, 21 % 10有余数, index为2// 即在数组中的索引为2(第三个元素)index = relative / size;}return index;}// 实现接口的link方法public LinkInfo link(Piece p1, Piece p2){// 两个Piece是同一个, 即选中了同一个方块, 返回nullif (p1.equals(p2))return null;// 如果p1的图片与p2的图片不相同, 则返回nullif (!p1.isSameImage(p2))return null;// 如果p2在p1的左边, 则需要重新执行本方法, 两个参数互换if (p2.getIndexX() < p1.getIndexX())return link(p2, p1);// 获取p1的中心点Point p1Point = p1.getCenter();// 获取p2的中心点Point p2Point = p2.getCenter();// 如果两个Piece在同一行if (p1.getIndexY() == p2.getIndexY()){// 它们在同一行并可以相连if (!isXBlock(p1Point, p2Point, GameConf.PIECE_WIDTH)){return new LinkInfo(p1Point, p2Point);}}// 如果两个Piece在同一列if (p1.getIndexX() == p2.getIndexX()){if (!isYBlock(p1Point, p2Point, GameConf.PIECE_HEIGHT)){// 它们之间没有真接障碍, 没有转折点return new LinkInfo(p1Point, p2Point);}}// 有一个转折点的情况// 获取两个点的直角相连的点, 即只有一个转折点Point cornerPoint = getCornerPoint(p1Point, p2Point,GameConf.PIECE_WIDTH, GameConf.PIECE_HEIGHT);if (cornerPoint != null){return new LinkInfo(p1Point, cornerPoint, p2Point);}// 该map的key存放第一个转折点, value存放第二个转折点,// map的size()说明有多少种可以连的方式Map<Point, Point> turns = getLinkPoints(p1Point, p2Point,GameConf.PIECE_WIDTH, GameConf.PIECE_WIDTH);if (turns.size() != 0){return getShortcut(p1Point, p2Point, turns,getDistance(p1Point, p2Point));}return null;}/*** 获取两个转折点的情况* @param point1* @param point2* @return Map对象的每个key-value对代表一种连接方式,*   其中key、value分别代表第1个、第2个连接点*/private Map<Point, Point> getLinkPoints(Point point1, Point point2,int pieceWidth, int pieceHeight){Map<Point, Point> result = new HashMap<Point, Point>();// 获取以point1为中心的向上, 向右, 向下的通道List<Point> p1UpChanel = getUpChanel(point1, point2.y, pieceHeight);List<Point> p1RightChanel = getRightChanel(point1, point2.x, pieceWidth);List<Point> p1DownChanel = getDownChanel(point1, point2.y, pieceHeight);// 获取以point2为中心的向下, 向左, 向上的通道List<Point> p2DownChanel = getDownChanel(point2, point1.y, pieceHeight);List<Point> p2LeftChanel = getLeftChanel(point2, point1.x, pieceWidth);List<Point> p2UpChanel = getUpChanel(point2, point1.y, pieceHeight);// 获取Board的最大高度int heightMax = (this.config.getYSize() + 1) * pieceHeight+ this.config.getBeginImageY();// 获取Board的最大宽度int widthMax = (this.config.getXSize() + 1) * pieceWidth+ this.config.getBeginImageX();// 先确定两个点的关系// point2在point1的左上角或者左下角if (isLeftUp(point1, point2) || isLeftDown(point1, point2)){// 参数换位, 调用本方法return getLinkPoints(point2, point1, pieceWidth, pieceHeight);}// p1、p2位于同一行不能直接相连if (point1.y == point2.y){// 在同一行// 向上遍历// 以p1的中心点向上遍历获取点集合p1UpChanel = getUpChanel(point1, 0, pieceHeight);// 以p2的中心点向上遍历获取点集合p2UpChanel = getUpChanel(point2, 0, pieceHeight);Map<Point, Point> upLinkPoints = getXLinkPoints(p1UpChanel,p2UpChanel, pieceHeight);// 向下遍历, 不超过Board(有方块的地方)的边框// 以p1中心点向下遍历获取点集合p1DownChanel = getDownChanel(point1, heightMax, pieceHeight);// 以p2中心点向下遍历获取点集合p2DownChanel = getDownChanel(point2, heightMax, pieceHeight);Map<Point, Point> downLinkPoints = getXLinkPoints(p1DownChanel,p2DownChanel, pieceHeight);result.putAll(upLinkPoints);result.putAll(downLinkPoints);}// p1、p2位于同一列不能直接相连if (point1.x == point2.x){// 在同一列// 向左遍历// 以p1的中心点向左遍历获取点集合List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth);// 以p2的中心点向左遍历获取点集合p2LeftChanel = getLeftChanel(point2, 0, pieceWidth);Map<Point, Point> leftLinkPoints = getYLinkPoints(p1LeftChanel,p2LeftChanel, pieceWidth);// 向右遍历, 不得超过Board的边框(有方块的地方)// 以p1的中心点向右遍历获取点集合p1RightChanel = getRightChanel(point1, widthMax, pieceWidth);// 以p2的中心点向右遍历获取点集合List<Point> p2RightChanel = getRightChanel(point2, widthMax,pieceWidth);Map<Point, Point> rightLinkPoints = getYLinkPoints(p1RightChanel,p2RightChanel, pieceWidth);result.putAll(leftLinkPoints);result.putAll(rightLinkPoints);}// point2位于point1的右上角if (isRightUp(point1, point2)){      // 获取point1向上遍历, point2向下遍历时横向可以连接的点Map<Point, Point> upDownLinkPoints = getXLinkPoints(p1UpChanel,p2DownChanel, pieceWidth);// 获取point1向右遍历, point2向左遍历时纵向可以连接的点Map<Point, Point> rightLeftLinkPoints = getYLinkPoints(p1RightChanel, p2LeftChanel, pieceHeight);// 获取以p1为中心的向上通道p1UpChanel = getUpChanel(point1, 0, pieceHeight);// 获取以p2为中心的向上通道p2UpChanel = getUpChanel(point2, 0, pieceHeight);// 获取point1向上遍历, point2向上遍历时横向可以连接的点Map<Point, Point> upUpLinkPoints = getXLinkPoints(p1UpChanel,p2UpChanel, pieceWidth);// 获取以p1为中心的向下通道p1DownChanel = getDownChanel(point1, heightMax, pieceHeight);// 获取以p2为中心的向下通道p2DownChanel = getDownChanel(point2, heightMax, pieceHeight);// 获取point1向下遍历, point2向下遍历时横向可以连接的点Map<Point, Point> downDownLinkPoints = getXLinkPoints(p1DownChanel,p2DownChanel, pieceWidth);// 获取以p1为中心的向右通道p1RightChanel = getRightChanel(point1, widthMax, pieceWidth);// 获取以p2为中心的向右通道List<Point> p2RightChanel = getRightChanel(point2, widthMax,pieceWidth);// 获取point1向右遍历, point2向右遍历时纵向可以连接的点Map<Point, Point> rightRightLinkPoints = getYLinkPoints(p1RightChanel, p2RightChanel, pieceHeight);// 获取以p1为中心的向左通道List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth);// 获取以p2为中心的向左通道p2LeftChanel = getLeftChanel(point2, 0, pieceWidth);// 获取point1向左遍历, point2向右遍历时纵向可以连接的点Map<Point, Point> leftLeftLinkPoints = getYLinkPoints(p1LeftChanel,p2LeftChanel, pieceHeight);result.putAll(upDownLinkPoints);result.putAll(rightLeftLinkPoints);result.putAll(upUpLinkPoints);result.putAll(downDownLinkPoints);result.putAll(rightRightLinkPoints);result.putAll(leftLeftLinkPoints);}// point2位于point1的右下角if (isRightDown(point1, point2)){// 获取point1向下遍历, point2向上遍历时横向可连接的点Map<Point, Point> downUpLinkPoints = getXLinkPoints(p1DownChanel,p2UpChanel, pieceWidth);// 获取point1向右遍历, point2向左遍历时纵向可连接的点Map<Point, Point> rightLeftLinkPoints = getYLinkPoints(p1RightChanel, p2LeftChanel, pieceHeight);// 获取以p1为中心的向上通道p1UpChanel = getUpChanel(point1, 0, pieceHeight);// 获取以p2为中心的向上通道p2UpChanel = getUpChanel(point2, 0, pieceHeight);// 获取point1向上遍历, point2向上遍历时横向可连接的点Map<Point, Point> upUpLinkPoints = getXLinkPoints(p1UpChanel,p2UpChanel, pieceWidth);// 获取以p1为中心的向下通道p1DownChanel = getDownChanel(point1, heightMax, pieceHeight);// 获取以p2为中心的向下通道p2DownChanel = getDownChanel(point2, heightMax, pieceHeight);// 获取point1向下遍历, point2向下遍历时横向可连接的点Map<Point, Point> downDownLinkPoints = getXLinkPoints(p1DownChanel,p2DownChanel, pieceWidth);// 获取以p1为中心的向左通道List<Point> p1LeftChanel = getLeftChanel(point1, 0, pieceWidth);// 获取以p2为中心的向左通道p2LeftChanel = getLeftChanel(point2, 0, pieceWidth);// 获取point1向左遍历, point2向左遍历时纵向可连接的点Map<Point, Point> leftLeftLinkPoints = getYLinkPoints(p1LeftChanel,p2LeftChanel, pieceHeight);// 获取以p1为中心的向右通道p1RightChanel = getRightChanel(point1, widthMax, pieceWidth);// 获取以p2为中心的向右通道List<Point> p2RightChanel = getRightChanel(point2, widthMax,pieceWidth);// 获取point1向右遍历, point2向右遍历时纵向可以连接的点Map<Point, Point> rightRightLinkPoints = getYLinkPoints(p1RightChanel, p2RightChanel, pieceHeight);result.putAll(downUpLinkPoints);result.putAll(rightLeftLinkPoints);result.putAll(upUpLinkPoints);result.putAll(downDownLinkPoints);result.putAll(leftLeftLinkPoints);result.putAll(rightRightLinkPoints);}return result;}/*** 获取p1和p2之间最短的连接信息* * @param p1* @param p2* @param turns 放转折点的map* @param shortDistance 两点之间的最短距离* @return p1和p2之间最短的连接信息*/private LinkInfo getShortcut(Point p1, Point p2, Map<Point, Point> turns,int shortDistance){List<LinkInfo> infos = new ArrayList<LinkInfo>();// 遍历结果Map,for (Point point1 : turns.keySet()){Point point2 = turns.get(point1);// 将转折点与选择点封装成LinkInfo对象, 放到List集合中infos.add(new LinkInfo(p1, point1, point2, p2));}return getShortcut(infos, shortDistance);}/*** 从infos中获取连接线最短的那个LinkInfo对象* * @param infos* @return 连接线最短的那个LinkInfo对象*/private LinkInfo getShortcut(List<LinkInfo> infos, int shortDistance){int temp1 = 0;LinkInfo result = null;for (int i = 0; i < infos.size(); i++){LinkInfo info = infos.get(i);// 计算出几个点的总距离int distance = countAll(info.getLinkPoints());// 将循环第一个的差距用temp1保存if (i == 0){temp1 = distance - shortDistance;result = info;}// 如果下一次循环的值比temp1的还小, 则用当前的值作为temp1if (distance - shortDistance < temp1){temp1 = distance - shortDistance;result = info;}}return result;}/*** 计算List<Point>中所有点的距离总和* * @param points 需要计算的连接点* @return 所有点的距离的总和*/private int countAll(List<Point> points){int result = 0;for (int i = 0; i < points.size() - 1; i++){// 获取第i个点Point point1 = points.get(i);// 获取第i + 1个点Point point2 = points.get(i + 1);// 计算第i个点与第i + 1个点的距离,并添加到总距离中result += getDistance(point1, point2);}return result;}/*** 获取两个LinkPoint之间的最短距离* * @param p1 第一个点* @param p2 第二个点* @return 两个点的距离距离总和*/private int getDistance(Point p1, Point p2){int xDistance = Math.abs(p1.x - p2.x);int yDistance = Math.abs(p1.y - p2.y);return xDistance + yDistance;}/*** 遍历两个集合, 先判断第一个集合的元素的x座标与另一个集合中的元素x座标相同(纵向), * 如果相同, 即在同一列, 再判断是否有障碍, 没有则加到结果的Map中去* * @param p1Chanel* @param p2Chanel* @param pieceHeight* @return*/private Map<Point, Point> getYLinkPoints(List<Point> p1Chanel,List<Point> p2Chanel, int pieceHeight){Map<Point, Point> result = new HashMap<Point, Point>();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);// 如果x座标相同(在同一列)if (temp1.x == temp2.x){// 没有障碍, 放到map中去if (!isYBlock(temp1, temp2, pieceHeight)){result.put(temp1, temp2);}}}}return result;}/*** 遍历两个集合, 先判断第一个集合的元素的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;}/*** 判断point2是否在point1的左上角* * @param point1* @param point2* @return p2位于p1的左上角时返回true,否则返回false*/private boolean isLeftUp(Point point1, Point point2){return (point2.x < point1.x && point2.y < point1.y);}/*** 判断point2是否在point1的左下角* * @param point1* @param point2* @return p2位于p1的左下角时返回true,否则返回false*/private boolean isLeftDown(Point point1, Point point2){return (point2.x < point1.x && point2.y > point1.y);}/*** 判断point2是否在point1的右上角* * @param point1* @param point2* @return p2位于p1的右上角时返回true,否则返回false*/private boolean isRightUp(Point point1, Point point2){return (point2.x > point1.x && point2.y < point1.y);}/*** 判断point2是否在point1的右下角* * @param point1* @param point2* @return p2位于p1的右下角时返回true,否则返回false*/private boolean isRightDown(Point point1, Point point2){return (point2.x > point1.x && point2.y > point1.y);}/*** 获取两个不在同一行或者同一列的座标点的直角连接点, 即只有一个转折点* * @param point1 第一个点* @param point2 第二个点* @return 两个不在同一行或者同一列的座标点的直角连接点*/private Point getCornerPoint(Point point1, Point point2, int pieceWidth,int pieceHeight){// 先判断这两个点的位置关系// point2在point1的左上角, point2在point1的左下角if (isLeftUp(point1, point2) || isLeftDown(point1, point2)){// 参数换位, 重新调用本方法return getCornerPoint(point2, point1, pieceWidth, pieceHeight);}// 获取p1向右, 向上, 向下的三个通道List<Point> point1RightChanel = getRightChanel(point1, point2.x,pieceWidth);List<Point> point1UpChanel = getUpChanel(point1, point2.y, pieceHeight);List<Point> point1DownChanel = getDownChanel(point1, point2.y,pieceHeight);// 获取p2向下, 向左, 向上的三个通道List<Point> point2DownChanel = getDownChanel(point2, point1.y,pieceHeight);List<Point> point2LeftChanel = getLeftChanel(point2, point1.x,pieceWidth);List<Point> point2UpChanel = getUpChanel(point2, point1.y, pieceHeight);if (isRightUp(point1, point2)){// point2在point1的右上角// 获取p1向右和p2向下的交点Point linkPoint1 = getWrapPoint(point1RightChanel, point2DownChanel);// 获取p1向上和p2向左的交点Point linkPoint2 = getWrapPoint(point1UpChanel, point2LeftChanel);// 返回其中一个交点, 如果没有交点, 则返回nullreturn (linkPoint1 == null) ? linkPoint2 : linkPoint1;}if (isRightDown(point1, point2)){// point2在point1的右下角// 获取p1向下和p2向左的交点Point linkPoint1 = getWrapPoint(point1DownChanel, point2LeftChanel);// 获取p1向右和p2向下的交点Point linkPoint2 = getWrapPoint(point1RightChanel, point2UpChanel);return (linkPoint1 == null) ? linkPoint2 : linkPoint1;}return null;}/*** 遍历两个通道, 获取它们的交点* * @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;}/*** 判断两个y座标相同的点对象之间是否有障碍, 以p1为中心向右遍历* * @param p1* @param p2* @param pieceWidth* @return 两个Piece之间有障碍返回true,否则返回false*/private boolean isXBlock(Point p1, Point p2, int pieceWidth){if (p2.x < p1.x){// 如果p2在p1左边, 调换参数位置调用本方法return isXBlock(p2, p1, pieceWidth);}for (int i = p1.x + pieceWidth; i < p2.x; i = i + pieceWidth){if (hasPiece(i, p1.y)){// 有障碍return true;}}return false;}/*** 判断两个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;}/*** 判断GamePanel中的x, y座标中是否有Piece对象* * @param x* @param y* @return true 表示有该座标有piece对象 false 表示没有*/private boolean hasPiece(int x, int y){if (findPiece(x, y) == null)return false;return true;}/*** 给一个Point对象,返回它的左边通道* * @param p* @param pieceWidth piece图片的宽* @param min 向左遍历时最小的界限* @return 给定Point左边的通道*/private List<Point> getLeftChanel(Point p, int min, int pieceWidth){List<Point> result = new ArrayList<Point>();// 获取向左通道, 由一个点向左遍历, 步长为Piece图片的宽for (int i = p.x - pieceWidth; i >= min; i = i - pieceWidth){// 遇到障碍, 表示通道已经到尽头, 直接返回if (hasPiece(i, p.y)){return result;}result.add(new Point(i, p.y));}return result;}/*** 给一个Point对象, 返回它的右边通道* * @param p* @param pieceWidth* @param max 向右时的最右界限* @return 给定Point右边的通道*/private List<Point> getRightChanel(Point p, int max, int pieceWidth){List<Point> result = new ArrayList<Point>();// 获取向右通道, 由一个点向右遍历, 步长为Piece图片的宽for (int i = p.x + pieceWidth; i <= max; i = i + pieceWidth){// 遇到障碍, 表示通道已经到尽头, 直接返回if (hasPiece(i, p.y)){return result;}result.add(new Point(i, p.y));}return result;}/*** 给一个Point对象, 返回它的上面通道* * @param p* @param min 向上遍历时最小的界限* @param pieceHeight* @return 给定Point上面的通道*/private List<Point> getUpChanel(Point p, int min, int pieceHeight){List<Point> result = new ArrayList<Point>();// 获取向上通道, 由一个点向右遍历, 步长为Piece图片的高for (int i = p.y - pieceHeight; i >= min; i = i - pieceHeight){// 遇到障碍, 表示通道已经到尽头, 直接返回if (hasPiece(p.x, i)){// 如果遇到障碍, 直接返回return result;}result.add(new Point(p.x, i));}return result;}/*** 给一个Point对象, 返回它的下面通道* * @param p* @param max 向上遍历时的最大界限* @return 给定Point下面的通道*/private List<Point> getDownChanel(Point p, int max, int pieceHeight){List<Point> result = new ArrayList<Point>();// 获取向下通道, 由一个点向右遍历, 步长为Piece图片的高for (int i = p.y + pieceHeight; i <= max; i = i + pieceHeight){// 遇到障碍, 表示通道已经到尽头, 直接返回if (hasPiece(p.x, i)){// 如果遇到障碍, 直接返回return result;}result.add(new Point(p.x, i));}return result;}
}

第五块:实现Activity

这一块相对比较简单,显示之前定义好的布局文件,为按钮注册监听器,对触碰事件的处理,游戏胜利的处理,游戏失败的处理等。

Activity用到前面定义的所有类和方法

这里还有一个工具类:GameConf.java, 负责管理游戏初始化的设置信息

package org.wwj.link.object;import android.content.Context;
//负责管理游戏的初始化设置信息的工具类
public class GameConf
{// 设置连连看的每个方块的图片的宽、高public static final int PIECE_WIDTH = 40;public static final int PIECE_HEIGHT = 40;// 记录游戏的总事件(100秒).public static int DEFAULT_TIME = 100;    // Piece[][]数组第一维的长度private int xSize;// Piece[][]数组第二维的长度private int ySize;// Board中第一张图片出现的x座标private int beginImageX;// Board中第一张图片出现的y座标private int beginImageY;// 记录游戏的总时间, 单位是秒private long gameTime;private Context context;/*** 提供一个参数构造器* * @param xSize Piece[][]数组第一维长度* @param ySize Piece[][]数组第二维长度* @param beginImageX Board中第一张图片出现的x座标* @param beginImageY Board中第一张图片出现的y座标* @param gameTime 设置每局的时间, 单位是秒* @param context 应用上下文*/public GameConf(int xSize, int ySize, int beginImageX,int beginImageY, long gameTime, Context context){this.xSize = xSize;this.ySize = ySize;this.beginImageX = beginImageX;this.beginImageY = beginImageY;this.gameTime = gameTime;this.context = context;}public long getGameTime(){return gameTime;}public int getXSize(){return xSize;}public int getYSize(){return ySize;}public int getBeginImageX(){return beginImageX;}public int getBeginImageY(){return beginImageY;}public Context getContext(){return context;}
}

Activity文件

这个游戏只需要用到一个Activity,所以相对而言比较简单,不存在信息之间的传递。

==>>Link.java

ackage org.wwj.link.Activity;import java.util.Timer;
import java.util.TimerTask;import org.wwj.link.board.GameService;
import org.wwj.link.board.impl.GameServiceImpl;
import org.wwj.link.object.GameConf;
import org.wwj.link.object.LinkInfo;
import org.wwj.link.view.GameView;
import org.wwj.link.view.Piece;import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;public class Link extends Activity
{// 游戏配置对象private GameConf config;// 游戏业务逻辑接口private GameService gameService;// 游戏界面private GameView gameView;// 开始按钮private Button startButton;// 记录剩余时间的TextViewprivate TextView timeTextView;// 失败后弹出的对话框private AlertDialog.Builder lostDialog;// 游戏胜利后的对话框private AlertDialog.Builder successDialog;// 定时器private Timer timer = new Timer();// 记录游戏的剩余时间private int gameTime;// 记录是否处于游戏状态private boolean isPlaying;// 振动处理类private Vibrator vibrator;// 记录已经选中的方块private Piece selected = null;private Handler handler = new Handler(){public void handleMessage(Message msg){switch (msg.what){case 0x123:timeTextView.setText("剩余时间: " + gameTime);gameTime--;// 时间小于0, 游戏失败if (gameTime < 0){stopTimer();// 更改游戏的状态isPlaying = false;lostDialog.show();return;}break;}}};@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);// 初始化界面init();}// 初始化游戏的方法private void init(){   //初始化游戏配置信息xSize,ySize,beginImageX,beginImgaeY,gameTime,contextconfig = new GameConf(8, 9, 2, 10 , 100000, this);// 得到游戏区域对象gameView = (GameView) findViewById(R.id.gameView);// 获取显示剩余时间的文本框timeTextView = (TextView) findViewById(R.id.timeText);// 获取开始按钮startButton = (Button) this.findViewById(R.id.startButton);// 获取振动器vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);gameService = new GameServiceImpl(this.config);gameView.setGameService(gameService);// 为开始按钮的单击事件绑定事件监听器startButton.setOnClickListener(new View.OnClickListener(){public void onClick(View source){startGame(GameConf.DEFAULT_TIME);}});// 为游戏区域的触碰事件绑定监听器this.gameView.setOnTouchListener(new View.OnTouchListener(){public boolean onTouch(View view, MotionEvent e){if (e.getAction() == MotionEvent.ACTION_DOWN){gameViewTouchDown(e);}if (e.getAction() == MotionEvent.ACTION_UP){gameViewTouchUp(e);}return true;}});// 初始化游戏失败的对话框lostDialog = createDialog("Lost", "游戏失败! 重新开始", R.drawable.lost).setPositiveButton("确定", new DialogInterface.OnClickListener(){public void onClick(DialogInterface dialog, int which){startGame(GameConf.DEFAULT_TIME);}});// 初始化游戏胜利的对话框successDialog = createDialog("Success", "游戏胜利! 重新开始",R.drawable.success).setPositiveButton("确定",new DialogInterface.OnClickListener(){public void onClick(DialogInterface dialog, int which){startGame(GameConf.DEFAULT_TIME);}});}@Overrideprotected void onPause(){// 暂停游戏stopTimer();super.onPause();}@Overrideprotected void onResume(){// 如果处于游戏状态中if (isPlaying){// 以剩余时间重写开始游戏startGame(gameTime);}super.onResume();}// 触碰游戏区域的处理方法private void gameViewTouchDown(MotionEvent event){// 获取GameServiceImpl中的Piece[][]数组Piece[][] pieces = gameService.getPieces();// 获取用户点击的x座标float touchX = event.getX();// 获取用户点击的y座标float touchY = event.getY();// 根据用户触碰的座标得到对应的Piece对象Piece currentPiece = gameService.findPiece(touchX, touchY);// 如果没有选中任何Piece对象(即鼠标点击的地方没有图片), 不再往下执行if (currentPiece == null)return;// 将gameView中的选中方块设为当前方块this.gameView.setSelectedPiece(currentPiece);// 表示之前没有选中任何一个Pieceif (this.selected == null){// 将当前方块设为已选中的方块, 重新将GamePanel绘制, 并不再往下执行this.selected = currentPiece;this.gameView.postInvalidate();return;}// 表示之前已经选择了一个if (this.selected != null){// 在这里就要对currentPiece和prePiece进行判断并进行连接LinkInfo linkInfo = this.gameService.link(this.selected,currentPiece);// 两个Piece不可连, linkInfo为nullif (linkInfo == null){// 如果连接不成功, 将当前方块设为选中方块this.selected = currentPiece;this.gameView.postInvalidate();}else{// 处理成功连接handleSuccessLink(linkInfo, this.selected, currentPiece, pieces);}}}// 触碰游戏区域的处理方法private void gameViewTouchUp(MotionEvent e){this.gameView.postInvalidate();}// 以gameTime作为剩余时间开始或恢复游戏private void startGame(int gameTime){// 如果之前的timer还未取消,取消timerif (this.timer != null){stopTimer();}// 重新设置游戏时间this.gameTime = gameTime;     // 如果游戏剩余时间与总游戏时间相等,即为重新开始新游戏if(gameTime == GameConf.DEFAULT_TIME){// 开始新的游戏游戏gameView.startGame();}isPlaying = true; this.timer = new Timer();// 启动计时器 , 每隔1秒发送一次消息this.timer.schedule(new TimerTask(){public void run(){handler.sendEmptyMessage(0x123);}}, 0, 1000);// 将选中方块设为null。this.selected = null;} /*** 成功连接后处理* * @param linkInfo 连接信息* @param prePiece 前一个选中方块* @param currentPiece 当前选择方块* @param pieces 系统中还剩的全部方块*/private void handleSuccessLink(LinkInfo linkInfo, Piece prePiece,Piece currentPiece, Piece[][] pieces){// 它们可以相连, 让GamePanel处理LinkInfothis.gameView.setLinkInfo(linkInfo);// 将gameView中的选中方块设为nullthis.gameView.setSelectedPiece(null);this.gameView.postInvalidate();// 将两个Piece对象从数组中删除pieces[prePiece.getIndexX()][prePiece.getIndexY()] = null;pieces[currentPiece.getIndexX()][currentPiece.getIndexY()] = null;// 将选中的方块设置null。this.selected = null;// 手机振动(100毫秒)this.vibrator.vibrate(100);// 判断是否还有剩下的方块, 如果没有, 游戏胜利if (!this.gameService.hasPieces()){// 游戏胜利this.successDialog.show();// 停止定时器stopTimer();// 更改游戏状态isPlaying = false;}}// 创建对话框的工具方法private AlertDialog.Builder createDialog(String title, String message,int imageResource){return new AlertDialog.Builder(this).setTitle(title).setMessage(message).setIcon(imageResource);}private void stopTimer(){// 停止定时器this.timer.cancel();this.timer = null;}
}

以上所有内容就是开发一个简单的连连看的过程和代码,这是本人初学这个游戏开发的一些总结,有很多内容都理解不够透彻,还有待加强整个游戏的逻辑分析能力。不过这只是开始,学习Android的时间也不算长,Java的知识也还只是处于初级阶段,我相信,经过长时间的沉淀,我的开发能力一定会变得更强的。这个连连看游戏,当做一次项目的练习,我会试着去开发属于自己的东西,不断加强自己的开发能力,增加项目经验,学习知识是一个漫长的过程,需要有足够的耐心去面对学习过程所遇到的麻烦和挫折,一个多月的学习以来,我深切感受了这一点,不过我会鼓足勇气继续迎接下一场挑战,wwj,未完待续。

项目开发-疯狂连连看游戏开发相关推荐

  1. 疯狂连连看之开发游戏界面组件一

    疯狂连连看之开发游戏界面组件一 开发游戏界面组件 本游戏的界面组件采用了一个自定义View:GameView,它从View基类派生而出,这个自定义View的功能就是根据游戏状态来绘制游戏界面上的全部方 ...

  2. Android疯狂连连看游戏

    今天看完了李刚老师的<疯狂Android讲义>一书中的第18章<疯狂连连看>,从而学会了如何编写一个简单的Android疯狂连连看游戏. 开发这个流行的小游戏,难度适中,而且能 ...

  3. 我的Android进阶之旅------gt;Android疯狂连连看游戏的实现之游戏效果预览(一)

    今天看完了李刚老师的<疯狂Android讲义>一书中的第18章<疯狂连连看>,从而学会了如何编写一个简单的Android疯狂连连看游戏. 开发这个流行的小游戏,难度适中,而且能 ...

  4. 区块链游戏开发颠覆传统游戏开发的 5 种方式

    区块链技术已在许多行业中使用,但它尤其扰乱了游戏行业.区块链游戏开发并不是一个新概念,但还是比较新的.公司现在正在寻找在他们的游戏中使用区块链技术的方法. 区块链游戏开发颠覆传统游戏开发的一些方式 包 ...

  5. android游戏开发组件,Android实现疯狂连连看游戏之开发游戏界面(二)

    连连看的游戏界面十分简单,大致可以分为两个区域: --游戏主界面区 --控制按钮和数据显示区 1.开发界面布局 本程序使用一个RelativeLayout作为整体的界面布局元素,界面布局上面是一个自定 ...

  6. Python项目实战学习 外星人入侵游戏开发总结

    外星人入侵开发总结 设置屏幕大小.游戏名称.背景颜色 pygame.init() #初始化背景设置ai_settings = Settings()screen = pygame.display.set ...

  7. win32游戏开发(2) --连连看游戏制作(vc++版)

    文章索引 源代码下载 工程目录一览 实现细节 源代码下载 附上下载链接:连连看vc++版源代码 工程目录一览 文件功能及关系图: GameEngine类 成员名 作用 static GameEngin ...

  8. unity开发入门_Unity游戏开发终极入门指南

    unity开发入门 Unity is a great tool for prototyping everything from games, to interactive visualisations ...

  9. nft游戏开发,nft游戏开发平台

    苹果公司将调解一起集体诉讼,即允许AppStore放开对区块链市场中中小规模开发者的限制,苹果公司将就此达成和解.由于iPhone的限制,这对于NFT游戏赛道来说是一个非常大的利好消息,很有可能就是这 ...

最新文章

  1. 22行代码AC——L1-023 输出GPLT(~解题报告~)
  2. 应用商店应用计算机,基于中国虹计算机的应用商店的设计与实现
  3. 韩流来袭哈狗帮_韩流可以教给我们什么设计知识
  4. Java中J.U.C扩展组件之ForkJoinTask和ForkJoinPool
  5. 扫地机自动回充揭秘之小米/iRobot/云鲸/360
  6. oracle认证071和061,【2019年8月版本】Oracle OCP认证 071考试原题-32
  7. 生信技能树课程记录笔记(六)20220530
  8. 查询所有银行在各省的分行、支行
  9. Android 12 源码下载、编译与烧录到Pixel 3a
  10. Ubuntu16.0.4 桌面美化 终端透明
  11. Android快速启动窗口技术
  12. java会签_工作流引擎会签,加签,主持人,组长模式 专题讲解
  13. Linux 30岁啦,这些历史你知道多少呢?
  14. python的math库函数汇总
  15. 关于sessions.ser文件的一些思考
  16. AUTOCAD——面域
  17. std::string::substr
  18. electron 设置窗口默认最大化、全屏
  19. [ERROR] Couldn‘t set the case sensitive attribute of the directory “\\?\C:\WSL\“.Reason: Indicates
  20. AV1:谷歌微软等联合打造的新视频压缩技术

热门文章

  1. MOOG伺服阀D634-501A
  2. MOOG伺服阀D633-7205
  3. 正宗港行诺基亚N70的极速鉴别方法
  4. 斯坦福公开课第6课笔记
  5. 微信小游戏 获取判群ID方法
  6. 逃离AI测肤“美学陷阱”:体素科技手握严肃皮肤病AI宝藏图
  7. 插播——滑动滤波算法
  8. OpenGL蓝宝书第七章:立体天空和纹理折射、双纹理(下)
  9. 欧洲药典查询-各国药典数据库查询入口
  10. 腾讯XR,为什么凉了