拼图虽是比较小的一个游戏,但涉及到的逻辑和代码也没那么简单,这里参考慕课网上的教程,采用一个二维数组来存储拼图的小方格,并将拼图的数据GameData(包括x,y坐标和正确的摆放位置)和视图(Bitmap)分离,并编写相应的游戏逻辑控制方法(控制层controller),很好地体现了MVC的思想。
视频网站:http://www.imooc.com/learn/683
效果图:
源代码:
package com.pintu;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
//利用二位数组创建若干个游戏小方块
private ImageView[][] iv_game_arr = new ImageView[3][5];
//游戏主界面
private GridLayout gl_main_game ;
//当前手势
private GestureDetector mDetector ;
private boolean isGameStart;
//保存当前空方块的实例
private ImageView iv_null ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
int type = getDirByGes(e1.getX() , e1.getY() , e2.getX() , e2.getY());
changeByDir(type , true );
return false;
}
});
setContentView(R.layout.activity_main);
//初始化游戏的若干个小方块
//获取一张大图
Bitmap bigBm = BitmapFactory.decodeResource(getResources(),R.drawable.pingtu);
//每个游戏小方块的宽和高
int width = bigBm.getWidth()/5 ;
for( int i = 0 ; i < iv_game_arr.length; i ++ ){
for( int j = 0 ; j < iv_game_arr[0].length ; j++ ){
//根据行和列切成若干个游戏小图片
Bitmap bm = Bitmap.createBitmap(bigBm , j * width , i * width , width , width) ;
iv_game_arr[i][j] = new ImageView(this);
iv_game_arr[i][j].setImageBitmap(bm);
iv_game_arr[i][j].setPadding(2,2,2,2);
iv_game_arr[i][j].setTag(new GameData(i , j , bm ));
iv_game_arr[i][j].setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean flag = isNearNullImageView((ImageView) v);
if(flag){
changeDataByImageView((ImageView) v);
}
}
});
}
}
//初始化游戏主界面,并添加若干个小方块
gl_main_game = (GridLayout) findViewById(R.id.gl_main_game);
for( int i = 0 ; i < iv_game_arr.length; i ++ ){
for( int j = 0 ; j < iv_game_arr[0].length ; j++ ){
gl_main_game.addView(iv_game_arr[i][j]);
}
}
//设置最后一个方块为空
setNullImageView(iv_game_arr[2][4]);
//初始化随机打乱顺序的方块
randomMove();
isGameStart = true ;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDetector.onTouchEvent(event);
}
public void changeByDir( int type ){
changeByDir(type , true);
}
/**
* 根据手势的方向,获取空方块相应的相邻位置,如果存在方块,那么进行数据交换
* @param type 1 : 上 , 2 : 下 , 3 : 左 , 4 : 右
* @param isAnim 是否有动画
*/
public void changeByDir( int type , boolean isAnim ){
//获取当前空方块的位置
GameData mNullGameData = (GameData) iv_null.getTag();
//根据方向,设置相应的相邻位置的坐标
int new_x = mNullGameData.x ;
int new_y = mNullGameData.y ;
if( type == 1){ //要移动的方块在当前空方块的下边
new_x ++ ;
}else if( type == 2 ){
new_x -- ;
}else if( type == 3 ){
new_y ++ ;
}else if( type == 4 ){
new_y -- ;
}
//判断这个新坐标是否存在
if( new_x >= 0 && new_x < iv_game_arr.length && new_y >= 0 && new_y < iv_game_arr[0].length){
//存在的话,开始移动
if( isAnim){
changeDataByImageView(iv_game_arr[new_x][new_y]);
}else {
changeDataByImageView(iv_game_arr[new_x][new_y],isAnim);
}
}else {
//什么也不做
}
}
/**
* 手势判断,向左滑还是向右滑
* @param start_x 手势的起始点x
* @param start_y 手势的起始点y
* @param end_x 手势的终止点x
* @param end_y 手势的终止点y
* @return 1 : 上 , 2 : 下 , 3 : 左 , 4 : 右
*/
public int getDirByGes( float start_x , float start_y , float end_x , float end_y ){
//是否是左右方向
boolean isLeftOrRight = (Math.abs(start_x - end_x) > Math.abs(start_y - end_y)) ? true : false;
if( isLeftOrRight ){//左右
boolean isLeft = start_x - end_x > 0 ? true : false ;
if( isLeft ){
return 3 ;
}else {
return 4 ;
}
}else {//上下
boolean isUp = start_y - end_y > 0 ? true : false ;
if( isUp ){
return 1 ;
}else {
return 2 ;
}
}
}
//随机打乱顺序
public void randomMove(){
//打乱的次数
for( int i = 0 ; i < 15; i ++ ){
//根据手势开始交换,无动画
int type = (int)(Math.random() * 4 ) + 1 ;
changeByDir(type,false);
}
}
public void changeDataByImageView(final ImageView imageView ){
changeDataByImageView(imageView,true);
}
/**
* 动画结束之后,交换两个方块的数据
*/
public void changeDataByImageView(final ImageView imageView , boolean isAnim){
if( !isAnim ){
GameData mGameData = (GameData) imageView.getTag();
iv_null.setImageBitmap(mGameData.bm);
GameData mNullGameData = (GameData) iv_null.getTag();
mNullGameData.bm = mGameData.bm ;
mNullGameData.p_x = mGameData.p_x ;
mNullGameData.p_y = mGameData.p_y ;
//设置当前点击的方块为空方块
setNullImageView(imageView);
if( isGameStart){
isGameOver();
}
return;
}
//创建一个动画,设置好方向,移动距离
TranslateAnimation translateAnimation = null ;
if( imageView.getX() > iv_null.getX() ){//当前点击的方块在空方块下边
//往上移动
translateAnimation = new TranslateAnimation(0.1f , -imageView.getWidth(), 0.1f , 0.1f );
}else if ( imageView.getX() < iv_null.getX() ){ //当前点击的方块在空方块上边
//往下移动
translateAnimation = new TranslateAnimation(0.1f , imageView.getWidth(), 0.1f , 0.1f );
}else if ( imageView.getY() > iv_null.getY() ){ //当前点击的方块在空方块右边
//往左移动
translateAnimation = new TranslateAnimation(0.1f , 0.1f , 0.1f , -imageView.getWidth() );
} else if ( imageView.getY() < iv_null.getY() ){ //当前点击的方块在空方块左边
//往右移动
translateAnimation = new TranslateAnimation(0.1f , 0.1f , 0.1f , imageView.getWidth() );
}
//设置动画的时长
translateAnimation.setDuration(70);
//设置动画结束后是否停留
translateAnimation.setFillAfter(true);
//动画结束后交换数据
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
imageView.clearAnimation();
GameData mGameData = (GameData) imageView.getTag();
iv_null.setImageBitmap(mGameData.bm);
GameData mNullGameData = (GameData) iv_null.getTag();
mNullGameData.bm = mGameData.bm ;
mNullGameData.p_x = mGameData.p_x ;
mNullGameData.p_y = mGameData.p_y ;
//设置当前点击的方块为空方块
setNullImageView(imageView);
if( isGameStart){
isGameOver();
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
//执行动画
imageView.startAnimation(translateAnimation);
}
/**
* 设置某个方块为空方块
* @param imageView 当前要设置为空的方块的实例
*/
public void setNullImageView( ImageView imageView){
imageView.setImageBitmap(null);
iv_null = imageView ;
}
/**
* 判读当前点击的方块与空白方块是否相邻
* @param imageView 所点击的方块
* @return true 相邻
*/
public boolean isNearNullImageView( ImageView imageView){
//分别获取当前空方块与点击方块的位置,通过想x,y两边都差1的方式判断
GameData mNullGameData = (GameData) iv_null.getTag();
GameData mGameData = (GameData) imageView.getTag();
if( mNullGameData.y == mGameData.y && mGameData.x+1 == mNullGameData.x){
//当前点击的方块在空方块上面
return true ;
}
else if( mNullGameData.y == mGameData.y && mGameData.x-1 == mNullGameData.x ){
//当前点击的方块在空方块下面
return true ;
}else if( mNullGameData.y == mGameData.y + 1 && mGameData.x == mNullGameData.x ){
//当前点击的方块在空方块左边
return true ;
}else if( mNullGameData.y == mGameData.y - 1 && mGameData.x == mNullGameData.x ){
//当前点击的方块在空方块右边
return true ;
}
return false ;
}
/**
* 每个游戏小方块上要绑定的数据
*/
class GameData{
//每个小方块的实际位置x
public int x = 0 ;
//每个小方块的实际位置y
public int y = 0 ;
//每个小方块的图片
public Bitmap bm ;
//每个小方块的图片的位置x
public int p_x = 0 ;
//每个小方块的图片的位置y
public int p_y = 0 ;
public GameData(int x, int y, Bitmap bm) {
this.x = x;
this.y = y;
this.bm = bm;
this.p_x = x;
this.p_y = y;
}
/**
* 判断小方块的位置是否正确
* @return true:正确 false:不正确
*/
public boolean isTrue() {
if( x == p_x && y == p_y ){
return true;
}
return false;
}
}
//判断游戏结束的方法
public void isGameOver(){
boolean isGameOver = true ;
//遍历每个游戏小方块
for( int i = 0 ; i < iv_game_arr.length ; i ++ ){
for( int j = 0 ; j < iv_game_arr[0].length ; j ++ ){
//为空的方块不判断,跳过
if( iv_game_arr[i][j] == iv_null ){
continue;
}
GameData mGameData = (GameData) iv_game_arr[i][j].getTag();
if( !mGameData.isTrue()){
isGameOver = false ;
break ;
}
}
}
if( isGameOver ){
Toast.makeText(this,"游戏结束",Toast.LENGTH_SHORT).show();
}
}
}
布局文件就是一个简单的GridLayout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.pintu.MainActivity">
<GridLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnCount="5"
android:rowCount="3"
android:id="@+id/gl_main_game"></GridLayout>
</RelativeLayout>