项目开发-疯狂连连看游戏开发
疯狂连连看游戏开发
游戏简介:疯狂连连看,是一款简单易玩的手机休闲游戏,界面布局简单,玩法简单,适合广大年龄层的用户进行休闲、放松。该游戏,应用于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.GameView
android: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">
<!-- 控制游戏开始的按钮 -->
<Button
android: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;
}
@Override
protected 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
{
@Override
protected 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加1
size += 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>();
// 遍历每个图片ID
for (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:
// 默认返回FullBoard
board = 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[][]数组中的第一维的索引值
// 第二个参数为每张图片的宽40
int indexX = getIndex(relativeX, GameConf.PIECE_WIDTH);
// 获取relativeY座标在Piece[][]数组中的第二维的索引值
// 第二个参数为每张图片的高40
int indexY = getIndex(relativeY, GameConf.PIECE_HEIGHT);
// 这两个索引比数组的最小索引还小, 返回null
if (indexX < 0 || indexY < 0)
{
return null;
}
// 这两个索引比数组的最大索引还大(或者等于), 返回null
if (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是同一个, 即选中了同一个方块, 返回null
if (p1.equals(p2))
return null;
// 如果p1的图片与p2的图片不相同, 则返回null
if (!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的还小, 则用当前的值作为temp1
if (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);
// 返回其中一个交点, 如果没有交点, 则返回null
return (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;
// 记录剩余时间的TextView
private 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;
}
}
};
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 初始化界面
init();
}
// 初始化游戏的方法
private void init()
{
//初始化游戏配置信息xSize,ySize,beginImageX,beginImgaeY,gameTime,context
config = 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);
}
});
}
@Override
protected void onPause()
{
// 暂停游戏
stopTimer();
super.onPause();
}
@Override
protected 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);
// 表示之前没有选中任何一个Piece
if (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为null
if (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还未取消,取消timer
if (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处理LinkInfo
this.gameView.setLinkInfo(linkInfo);
// 将gameView中的选中方块设为null
this.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,未完待续。