从拼图游戏开始(六)_游戏主体的Android实现

       好的,要开始正式编码了。说实话目前为止笔者也不知道这个东西该怎么写,只是觉得能够完成它,于是就写了。以至于这种边想边写的程序,必然会存在一些需要优化的地方,但是这里笔者也仅是抛砖引玉,不敢自称高手。

       今天写的是游戏主体的实现,因为不想一上来就摆一大堆实体类、字段名,看的人头大。所以本文论述的仅仅是游戏主题的实现,暂时不考虑数据持久化时的字段等细节,而诸如字段这样的细节,会在后面的文章中统一论述。

       先上效果图,代码可在本文最后下载:

       看上去有点卡,这是因为模拟器和PC屏幕录制软件的问题,笔者可以保证在真机上效果很流畅。这里有以下几点需要论述一下:      

       随机问题数据的生成方法,通常有两种方法:

       一、随机的交换矩阵中的任意两个元素,这种方式生成的问题,求解过程通常十分耗时。

       二、从目标状态开始随机移动,造成一种随机的分布,在随机移动步数不大时,这种生成方法比较容易求解。

      本程序采用第一种方法生成随机数据,但是目前演示时为了避免过程时间的求解等待时间,这里使用了一个固定问题。读者也可以手动移动问题后,再单击“自动”按钮进行求解。

       随机问题数据的可解性讨论:

       可以参见本人的另外一篇文章:从拼图游戏开始(三)_可行解的讨论

       问题的求解方法:

       可以参考本人的另外一篇文章:从拼图游戏开始(四)_IDA*算法求解Java实现

         实现中需要注意的细节:

         一、单元格的移动使用Animation和View.setLayoutParams()实现,注意需要在AnimationListener中的onAnimationEnd()方法中清除组件上的Animation,否则会出现抖动现象。

       二、自定义组件GamePanel类,继承自RelativeLayout,向其中添加自定义"方格"类TileView,注意如果需要自定义GamePanel的尺寸,则需要重写onMeasure()方法。GamePanel类的实现代码在本文最后。

         以下是部分代码:

       游戏主体GamePanel

package com.wly.puzzle15;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.RelativeLayout;

/**
 * 游戏单元,一个自定义的组件,封装了组件和事件
 * @author wly
 *
 */
public class GamePanel extends RelativeLayout implements OnClickListener {


	//当前游戏数据
	private int[][] mData;	
	//游戏中大图片被切割后的小图片数组
	private Bitmap[] bitmaps;
	//当前"空格"的位置
	private int blank_row;
	private int blank_column;
		
	TileView[][] tiles;
	
	private int tileWidth;
	private int tileHeight;
	private Context mContext;
	private Handler mHandler;
	
	//自动机
	private AutoRunner autoRunner;
	
//	//当前游戏状态
//	private int mState;
//	//当前步骤
//	private int mCost;
//	//游戏中用到的大图片
//	private Bitmap originalBitmap;
//	//最后一次玩的时间
//	private long lastPlay;
	
	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			move(msg.getData().getInt("direction"),
					msg.getData().getInt("row"), 
					msg.getData().getInt("column"));
		}
	};
	
	public GamePanel(Context context,Handler handler) {
		super(context);
		this.mContext = context;
		this.mHandler = handler;
		init(context);
	}
	
	public Context getMContext() {
		return mContext;
	}
	

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
	}

	
	public void autoRun() {
		if(autoRunner != null) {
			autoRunner.cancel();
		}
		autoRunner = new AutoRunner(this,handler, new SolvePuzzleListener() {
			
			@Override
			public void start() {
				//发送消息给MainActivity,显示加载进度条
				Message msg = new Message();
				msg.what = 0;
				mHandler.sendMessage(msg);
			}
			
			@Override
			public void finished() {
				//发送消息给MainActivity,隐藏加载进度条
				Message msg = new Message();
				msg.what = 1;
				mHandler.sendMessage(msg);
			}
		});
		autoRunner.start();
	}


	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		super.onMeasure(tileWidth * Conf.SIZE, tileHeight * Conf.SIZE);
	}

	/**
	 * 重新随机生成问题数据
	 */
	public void init(Context context) {
		
		this.removeAllViews();
		
		this.mData = new PuzzleGenerator().getPuzzleData();
		for(int i=0;i<mData.length;i++) {
			for(int j=0;j<mData.length;j++) {
				if(mData[i][j] == 0) {
					blank_row = i;
					blank_column = j;
				}
			}
		}
		this.setBackgroundColor(Conf.panelBG);
		

		bitmaps = new Bitmap[Conf.SIZE * Conf.SIZE];
	
		tiles = new TileView[Conf.SIZE][Conf.SIZE];
		
		
		bitmaps[0]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no0);
		bitmaps[1]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no1);
		bitmaps[2]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no2);
		bitmaps[3]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no3);
		bitmaps[4]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no4);
		bitmaps[5]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no5);
		bitmaps[6]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no6);
		bitmaps[7]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no7);
		bitmaps[8]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no8);
		bitmaps[9]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no9);
		bitmaps[10]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no10);
		bitmaps[11]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no11);
		bitmaps[12]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no12);
		bitmaps[13]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no13);
		bitmaps[14]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no14);
		bitmaps[15]=BitmapFactory.decodeResource(context.getResources(), R.drawable.no15);
		
		tileWidth = bitmaps[0].getWidth();
		tileHeight = bitmaps[0].getHeight();
		
		RelativeLayout.LayoutParams containerParams = 
				new RelativeLayout.LayoutParams(Conf.SIZE * tileWidth,Conf.SIZE * tileHeight);
		this.setLayoutParams(containerParams);
		
		for(int i=0;i<Conf.SIZE;i++) {
			for(int j=0;j<Conf.SIZE;j++) {
				tiles[i][j] = new TileView(context,mData[i][j],i,j);
				tiles[i][j].setImageBitmap(bitmaps[mData[i][j]]);
//				tiles[i][j].setId(i*Conf.SIZE + j + 2000);
//				tiles[i][j].setOnTouchListener(this);
				tiles[i][j].setOnClickListener(this);
				RelativeLayout.LayoutParams layoutParams = 
						new RelativeLayout.LayoutParams(tileWidth, tileHeight);
			    layoutParams.leftMargin = tileWidth * j;
			    layoutParams.topMargin = tileHeight * i;
			    tiles[i][j].setLayoutParams(layoutParams);
			    
			    if(mData[i][j] != 0) { //不添加"空格"
					this.addView(tiles[i][j]);
			    } 
			}
		}
	}
	
	public int getBlankRow() {
		return blank_row;
	}
	
	public int getBlankColumn() {
		return blank_column;
	}
	
	/**
	 * 得到当前单击单元的可以移动的方向
	 * @return
	 */
	public int getMoveDirection(int row,int column) {
		if(row > 0 && tiles[row-1][column].getData() == 0) {
			return Conf.UP;
		} else if(row < (mData.length-1) 
				&& tiles[row+1][column].getData() == 0) {
			return Conf.DOWN;
		} else if(column > 0 && tiles[row][column-1].getData() == 0) {
			return Conf.LEFT;
		} else if(column < (mData[0].length-1) 
				&& tiles[row][column+1].getData() == 0) {
			return Conf.RIGHT;
		} else {
			return Conf.UNMOVABLE;
		}
	}
	
	/**
	 * 移动数据,交换数据元素
	 * @param array
	 * @param row
	 * @param column
	 * @param direction
	 */
	private void moveData(int[][] array,TileView[][] tiles,int row,int column,int direction) {
		int temp = 0;
		TileView tempView;
		
		switch(direction) {
		case Conf.UP:
			temp = array[row][column];
			array[row][column] = array[row-1][column];
			array[row-1][column] = temp;
			
			//设置TileView的位置标记
			tempView = tiles[row][column];
			tiles[row][column] = tiles[row-1][column];
			tiles[row][column].setRow(row+1);
			tiles[row-1][column] = tempView;
			tiles[row-1][column].setRow(row-1);
			blank_row ++;
			break;
		case Conf.DOWN:
			temp = array[row][column];
			array[row][column] = array[row+1][column];
			array[row+1][column] = temp;
			
			//设置TileView的位置标记
			tempView = tiles[row][column];
			tiles[row][column] = tiles[row+1][column];
			tiles[row][column].setRow(row-1);
			tiles[row+1][column] = tempView;
			tiles[row+1][column].setRow(row+1);
			blank_row --;
			break;
		case Conf.LEFT:
			temp = array[row][column];
			array[row][column] = array[row][column-1];
			array[row][column-1] = temp;
			
			tempView = tiles[row][column];
			tiles[row][column] = tiles[row][column-1];
			tiles[row][column].setColumn(column+1);
			tiles[row][column-1] = tempView;
			tiles[row][column-1].setColumn(column-1);
			blank_column ++;
			break;
		case Conf.RIGHT:
			temp = array[row][column];
			array[row][column] = array[row][column+1];
			array[row][column+1] = temp;
			
			//设置TileView的位置标记
			tempView = tiles[row][column];
			tiles[row][column] = tiles[row][column+1];
			tiles[row][column].setColumn(column-1);
			tiles[row][column+1] = tempView;
			tiles[row][column+1].setColumn(column+1);
			blank_column --;
			break;
		case Conf.UNMOVABLE:
			break;
		}
	}
	
	public void printMatirx(int[][] array) {
		for(int i=0;i<array.length;i++) {
			for(int j=0;j<array[0].length;j++) {
				System.out.print(array[i][j] + " ");
			}
			System.out.println();
		}
	}

	@Override
	public void onClick(final View v) {
		if(v instanceof TileView) {
			
			int row = ((TileView) v).getRow();
			int column = ((TileView) v).getColumn();
			int direction = getMoveDirection(row,column);
			move(direction, row, column);
		}
	}
	
	
	
	public int[][] getData() {
		return mData;
	}



	public void setData(int[][] mData) {
		this.mData = mData;
	}


	

	/**
	 * 自动机解题调用方法
	 * @param direction
	 */
	public void move(int direction,int row,int column) {
		final TileView v = tiles[row][column];
		TranslateAnimation tAnimation = null;
		final RelativeLayout.LayoutParams layoutParams = 
				new RelativeLayout.LayoutParams(tileWidth,tileHeight);
		direction = getMoveDirection(row,column);
		
	    moveData(mData,tiles,row, column, direction);
	    printMatirx(mData);
		switch(direction) {
		case Conf.UP:
			tAnimation = new TranslateAnimation(0, 
					0, 0, -tileHeight);
		    layoutParams.leftMargin = v.getLeft();
		    layoutParams.topMargin = v.getTop() - tileHeight;
			break;
		case Conf.DOWN:
			tAnimation = new TranslateAnimation(0, 
					0, 0, tileHeight);
		    layoutParams.leftMargin = v.getLeft();
		    layoutParams.topMargin = v.getTop() + tileHeight;
			break;
		case Conf.LEFT:
			tAnimation = new TranslateAnimation(0, 
					-tileWidth, 0, 0);
		    layoutParams.leftMargin = v.getLeft() - tileWidth;
		    layoutParams.topMargin = v.getTop();
			break;
		case Conf.RIGHT:
			tAnimation = new TranslateAnimation(0, 
					tileWidth, 0, 0);
		    layoutParams.leftMargin = v.getLeft() + tileWidth;
		    layoutParams.topMargin = v.getTop();
			break;
		case Conf.UNMOVABLE:
			break;
		}
		
		if(tAnimation != null) { //可能单击了不可移动的位置
			tAnimation.setDuration(500);
			v.startAnimation(tAnimation);
			tAnimation.setFillAfter(false);
			tAnimation.setAnimationListener(new AnimationListener() {
				
				@Override
				public void onAnimationStart(Animation animation) {
				}
				
				@Override
				public void onAnimationRepeat(Animation animation) {
				}
				
				@Override
				public void onAnimationEnd(Animation animation) {
					//取消组件上的动画,来避免发生闪烁
					v.clearAnimation();
					//设置Layout,因为使用Animation移动的图片,并没有移动焦点
				    v.setLayoutParams(layoutParams);
				}
			});			
		}
		
		GamePanel.this.invalidate();
	}
}

          自动求解机AutoRunner

package com.wly.puzzle15;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

/**
 * 一个线程做的自动机
 * @author wly
 *
 */
public class AutoRunner extends Thread{
	
	//每次移动单元的间隔时间
	private long MOVE_TIME = 1000;
	
	private GamePanel gamePanel;
	//用于存放解的步骤:0,1,2,3
	private int[] moves = new int[1000];
	
	private boolean isAlive = false;
	//因为求解问题是一个比较耗时的动作,所以这里做了一个回调接口,来显示加载进度条
	private SolvePuzzleListener mListener;
	private Handler mHandler;
	public AutoRunner(GamePanel gamePanel,Handler handler,SolvePuzzleListener listener) {
		this.gamePanel = gamePanel;
		this.mListener = listener;
		this.mHandler = handler;
	}

	@Override
	public void run() {
		super.run();
		mListener.start();
		IDAStarAlgorithm idaStarAlgorithm = new IDAStarAlgorithm(gamePanel.getData());
		moves = idaStarAlgorithm.getSolvePath(gamePanel.getData());
		mListener.finished();
		int i = 0;
		isAlive = true;
		while(isAlive && moves[i] != -1) {
			Message msg = new Message();
			Intent intent = new Intent();
			Bundle bundle = new Bundle();
			switch(moves[i]) {
			case Conf.UP:
				bundle.putInt("row", gamePanel.getBlankRow()-1);
				bundle.putInt("column", gamePanel.getBlankColumn());
				bundle.putInt("direction", Conf.DOWN);
				msg.setData(bundle);
				mHandler.sendMessage(msg);
				break;
			case Conf.DOWN:
				bundle.putInt("row", gamePanel.getBlankRow()+1);
				bundle.putInt("column", gamePanel.getBlankColumn());
				bundle.putInt("direction", Conf.UP);
				msg.setData(bundle);
				mHandler.sendMessage(msg);
				break;
			case Conf.LEFT:
				bundle.putInt("row",gamePanel.getBlankRow());
				bundle.putInt("column", gamePanel.getBlankColumn()-1);
				bundle.putInt("direction", Conf.RIGHT);
				msg.setData(bundle);
				mHandler.sendMessage(msg);
				break;
			case Conf.RIGHT:
				bundle.putInt("row",gamePanel.getBlankRow());
				bundle.putInt("column", gamePanel.getBlankColumn()+1);
				bundle.putInt("direction", Conf.LEFT);
				msg.setData(bundle);
				mHandler.sendMessage(msg);
				break;
			}
			i++;
			try {
				this.sleep(MOVE_TIME);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	

	public void cancel() {
		isAlive = false;
	}
}


/**
 * 表示当前求解问题的回调接口
 * @author wly
 *
 */
interface SolvePuzzleListener {
	public void start();
	public void finished();
}

        问题生成器PuzzleGenerator

package com.wly.puzzle15;

/**
 * 游戏数据生成器
 * 其中关于问题的可解性讨论,可参考:
 * http://blog.csdn.net/u011638883/article/details/17139739
 * @author wly
 *
 */
public class PuzzleGenerator {

	
	/**
	 * 得到一个可解的问题数据
	 * @return
	 */
	public int[][] getPuzzleData() {

//-------------------------
//随机生成问题数据正确方法
//-------------------------
//		int[][] data = new int[Conf.SIZE][Conf.SIZE];
//		for(int i=0;i<data.length;i++) {
//			for(int j=0;j<data.length;j++) {
//				data[i][j] = i*data.length + j;
//			}
//		}
//		for(int i=0;i<data.length;i++) {
//			for(int j=0;j<data.length;j++) {
//				int index1 = (int)(Math.random() * Conf.SIZE);
//				int index2 = (int)(Math.random() * Conf.SIZE);
//				int temp = data[index1][index2];
//				data[index1][index2] = data[i][j];
//				data[i][j] = temp;
//			}
//		}

//-------------------------
//简单测试数据,仅作演示之用
//-------------------------
		int[][] data = {
				{5,1,3,4},
				{9,2,6,8},
				{13,10,7,11},
				{0,14,15,12}
		};
		
		//检查问题是否可解
		if(canSolve(data)) {
			return data;
		} else {
			return getPuzzleData();
		}
		
	}
	
	/**
	 * 讨论问题的可解性
	 * @param state 状态
	 */
	private boolean canSolve(int[][] state) {
		
		int blank_row = 0; //"空格"所在的行数
		
		for(int i=0;i<state.length;i++) {
			for(int j=0;j<state.length;j++) {
				if(state[i][j] == 0) {
					blank_row = i;
				}
			}
		}
		if(state.length % 2 == 1) { //问题宽度为奇数
			return (getInversions(state) % 2 == 0);
		} else { //问题宽度为偶数
			if((state.length - blank_row) % 2 == 1) { //从底往上数,空格位于奇数行
				return (getInversions(state) % 2 == 0);
			} else { //从底往上数,空位位于偶数行
				return (getInversions(state) % 2 == 1);
			}
		}
	}
	
	/**
	 * 计算问题的"倒置变量和"
	 * @param state
	 */
	private int getInversions(int[][] state) {
		int inversion = 0;
		int temp = 0;
		for(int i=0;i<state.length;i++) {
			for(int j=0;j<state[i].length;j++) {
				int index = i* state.length + j + 1;
				while(index < (state.length * state.length)) {
					if(state[index/state.length][index%state.length] != 0 
							&& state[index/state.length]
									[index%state.length] < state[i][j]) {
						temp ++;
					}
					index ++;
				}
				inversion = temp + inversion;
				temp = 0;
			}
		}
		return inversion;
	}
}

       完整工程下载:http://download.csdn.net/detail/u011638883/6761927

       O啦~~~

       转载请保留出处:http://blog.csdn.net/u011638883/article/details/17401179

       谢谢!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值