`
bcyy
  • 浏览: 1818586 次
文章分类
社区版块
存档分类
最新评论

项目开发-疯狂连连看游戏开发

 
阅读更多

疯狂连连看游戏开发

游戏简介:疯狂连连看,是一款简单易玩的手机休闲游戏,界面布局简单,玩法简单,适合广大年龄层的用户进行休闲、放松。该游戏,应用于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,未完待续。

分享到:
评论

相关推荐

    这是《我的Android进阶之旅--疯狂连连看游戏的实现》的源代码.zip

    Bootstrap:一个用于快速开发响应式网站的前端框架。 jQuery:一个流行的JavaScript库,简化了处理HTML文档、事件处理、动画等操作。 Webpack 和 Babel:前端构建工具,用于打包、转译和优化前端资源。 后端技术: ...

    疯狂连连看游戏源码

    疯狂连连看游戏源码,可以参照这个自己开发一款类型游戏。

    Android实现疯狂连连看游戏之开发游戏界面(二)

    主要为大家详细介绍了Android实现疯狂连连看游戏之开发游戏界面,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    疯狂android讲义第二版 疯狂连连看 电子拍卖系统 实例部分源代码

    第18章 疯狂连连看 18.1 连连看游戏简介 18.2 开发游戏界面 18.2.1 开发界面布局 18.2.2 开发游戏界面组件 18.2.3 处理方块之间的连接线 18.3 连连看的状态数据模型 18.3.1 定义数据模型 18.3.2 初始化游戏状态数据 ...

    Android实现疯狂连连看游戏之游戏效果预览(一)

    今天看完了李刚老师的《疯狂Android讲义》一书中的第18章《疯狂连连看》,从而学会了如何编写一个简单的Android疯狂连连看游戏。  开发这个流行的小游戏,难度适中,而且能充分激发学习热情,适合Android初学者来说...

    MyPictureMatching:这是《我的Android进阶之旅------&gt; Android疯狂连连看游戏的实现》的源代码

    Android疯狂连连看游戏的实现》的源代码 这是《我的Android进阶之旅------&gt; Android疯狂连连看游戏的实现》的源博客地址: : 下面是该游戏的预览图片 1,游戏最开始的界面,可以点击“开始”按钮开始游戏 2,...

    Android疯狂连连看代码

    安卓单机休闲游戏开发的界面组件、基本方法和实现游戏的Activity

    Android实现疯狂连连看游戏之加载界面图片和实现游戏Activity(四)

    正如在《我的Android进阶之旅——&gt;Android疯狂连连看游戏的实现之状态数据模型(三)》一文中看到的,在AbstractBoard的代码中,当程序需要创建N个Piece对象时,程序会直接调用ImageUtil的getPlayImages()方法去获取...

    疯狂Java讲义(第三版)-项目源代码

    疯狂Java讲义第三版,随书光盘项目源码,总共包括23个实例项目源码,内容如下: gobang: 第1章 控制台五子棋 cal: 第2章 仿Windows计算器 viewer: 第3章 图片浏览器 ball: 第4章 桌面弹球 tetris: 第...

    android 版本的疯狂连连看源代码

    超完整的一个android连连看源代码 android开发实例 android新手入门必看的工程实例

    疯狂java实例-第7章_单机连连看

    详细的教大家如何使用Java去开发一款属于自己的单机连连看。

    疯狂安卓讲义第2版--par3共3部分

    本书最后还提供了两个实用的案例:疯狂连连看和电子拍卖系统Android客户端,具有极高的参考价值。  《疯狂Android讲义(第2版)》适合有一定Java编程基础的读者。如果读者已熟练掌握Java编程语法并具有一定图形界面...

    疯狂安卓讲义第2版--par1

    本书最后还提供了两个实用的案例:疯狂连连看和电子拍卖系统Android客户端,具有极高的参考价值。  《疯狂Android讲义(第2版)》适合有一定Java编程基础的读者。如果读者已熟练掌握Java编程语法并具有一定图形界面...

    疯狂安卓讲义第2版--par2

    本书最后还提供了两个实用的案例:疯狂连连看和电子拍卖系统Android客户端,具有极高的参考价值。  《疯狂Android讲义(第2版)》适合有一定Java编程基础的读者。如果读者已熟练掌握Java编程语法并具有一定图形界面...

    疯狂java实战演义 光盘源码

    《 疯狂Java实战演义》以15个生动的Java案例,引领读者体验Java开发的乐趣。书中使用Java的Swing技术开发了若干个游戏,从这些游戏中可以了解到,Java一样可以做出优秀的游戏和应用程序。本书知识点丰富,适合有一定...

    Android实现疯狂连连看游戏之状态数据模型(三)

    对于游戏玩家而言,游戏...对于上图所示的数据模型,只要让数值为0的网格上不绘制图片,其他数值的网格则绘制相应的图片,就可以显示出连连看的游戏界面了。 本程序采用Piece[][]来保存游戏的状态模型,因为Piece对象

    疯狂Android讲义(第2版)完整清晰版 part2

    本书最后还提供了两个实用的案例:疯狂连连看和电子拍卖系统Android客户端,具有极高的参考价值。  《疯狂Android讲义(第2版)》适合有一定Java编程基础的读者。如果读者已熟练掌握Java编程语法并具有一定图形...

    疯狂java实战演义(pdf+codes)

    疯狂java实战演义(包括pdf 和源代码 压缩后17.5m) gobang: 第1章 控制台五子棋 cal: 第2章 仿Windows计算器 viewer: 第3章 图片浏览器 ball: 第4章 桌面弹球 tetris: 第5章 俄罗斯方块 image: 第6章 仿...

Global site tag (gtag.js) - Google Analytics