zoukankan      html  css  js  c++  java
  • 从零開始开发Android版2048 (三)逻辑推断

              近期工作比較忙,所以更新的慢了一点,今天的主要内容是关于Android版2048的逻辑推断,经过本篇的解说,基本上完毕了这个游戏的主体部分。

             首先还是看一下,我在实现2048时用到的一些存储的数据结构。

    我在实现时,为了省事存储游戏过程中的变量主要用到的是List。

    比方说:List<Integer> spaceList = new ArrayList<Integer>();这个spaceList主要用于保存。全部空白格的位置,也就是空白格在GridLayout中的位置(从0到15)

             对于数字格,以及格子相应的数据。我写了一个类例如以下:

    package com.example.t2048;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import android.util.Log;
    
    /**
     * 用于保存数字格,已经数字格相应的数字
     * @author Mr.Wang
     * 
     */
    public class NumberList {
    
    	//这个list用于保存全部不为空的格子的坐标(在GridLayout中的位置从0到15)
    	private List<Integer> stuffList = new ArrayList<Integer>();
    	//这个list用于保存全部不为空的格子相应的数字(以2为底数的指数)
    	private List<Integer> numberList = new ArrayList<Integer>();
    	
    	/**
    	 * 新增加的数字格
    	 * @param index   数字格相应的位置
    	 * @param number  相应数字的指数(以2为底数)
    	 */
    	public void add(int index, int number){
    		stuffList.add(index);
    		numberList.add(number);
    	}
    	
    	/**
    	 * 用于推断当前位置是否为数字格
    	 * @param index  当前位置
    	 * @return true表示是
    	 */
    	public boolean contains(int index){
    		return stuffList.contains(index);
    	}
    	
    	/**
    	 * 将当前的格子从数字列表中去掉
    	 * @param index
    	 */
    	public void remove(int index){
    		int order = stuffList.indexOf(index);
    		numberList.remove(order);
    		stuffList.remove(order);
    	}
    	
    	/**
    	 * 将当前格子相应的数字升级,指数加1
    	 * @param index
    	 */
    	public void levelup(int index){
    		int order = stuffList.indexOf(index);
    		numberList.set(order, numberList.get(order)+1);
    	}
    	
    	/**
    	 * 将当前格子相应的位置置换为新的位置
    	 * @param index      当前位置
    	 * @param newIndex   新的位置
    	 */
    	public void changeIndex(int index, int newIndex){
    		stuffList.set(stuffList.indexOf(index), newIndex);
    	}
    	
    	/**
    	 * 通过格子相应的位置获取其相应的数字
    	 * @param index   当前位置
    	 * @return        格子相应数字的指数
    	 */
    	public int getNumberByIndex(int index){
    		int order = stuffList.indexOf(index);
    		return numberList.get(order) ;
    	}
    	
    	
    	public String toString(){
    		return stuffList.toString()+numberList.toString();
    	}
    	
    	public void printLog(){
    		Log.i("stuffList", stuffList.toString());
    		Log.i("numberList", numberList.toString());
    	}
    }
    

                 这个类主要是我对数字格、数字格相应数字的保存,和增删改等操作。

    事实上就是两个list,我为了操作起来方便,所以把他们写在一个类里。


    然后,我们来讲一下这个游戏的逻辑。

    比方,我们在游戏过程中运行了一次向右滑动的操作,在这个操作中,我们要对全部能够移动和合并的格子进行推断和对应的操作:

    1、数字格的右边假设是空白格。则数字格与空白格交换

    2、数字格右边假设有多个空白格,则数字格与连续的最后一个空白格做交换

    3、数字格的右边假设存在与之同样的数字格,则本格置空。右边的数字格升级(指数加一)

    4、假设滑动方向连续存在多个同样的数字格,右的格子优先升级

    5、在一次滑动中,每一个格子最多升级一次

    当一个格子存在上述前四种中的随意一种时,则完毕了对它的操作

    我们试着把上面的推断规则翻译成代码,首先,明白在GridLayout中的坐标位置。我在GridLayout中採用的是水平布局,所以每一个格子相应的位置例如以下


    在这个基础上,我建立例如以下的坐标轴,以左上角为原点,x轴为横轴,y轴为竖轴:



    当向右滑动的时候,从上面逻辑来看,为了方便,我们应当从右向左遍历格子

    			for(int y=0;y<4;y++){
    				for(int x=2;x>=0;x--){
    					int thisIdx = 4*y +x;
    					Change(thisIdx,direction);
    				}
    			}

    每遍历到一个新的格子,运行一次change()方法。事实上应该是每遍历到一个非空的格子。运行一次change()可是我为了省事,把非空推断加到了change的代码里。我们看一下change()这种方法的实现。这种方法主要是用来推断,一个格子是须要移动、合并,还是什么都不操作。:

    	/**
    	 * 该方法,为每一个符合条件的格子运行变动的操作。如置换,升级等
    	 * @param thisIdx     当前格子的坐标
    	 * @param direction   滑动方向
    	 */
    	public void Change(int thisIdx,int direction){
    		if(numberList.contains(thisIdx)){
    						
    			int nextIdx = getLast(thisIdx, direction);
    			if(nextIdx == thisIdx){
    				//不能移动
    				return;
    			}else if(spaceList.contains(nextIdx)){
    				//存在能够置换的空白格
    				replace(thisIdx,nextIdx);
    			}else{				
    				if(numberList.getNumberByIndex(thisIdx) == numberList.getNumberByIndex(nextIdx)){
    					//能够合并
    					levelup(thisIdx, nextIdx);
    				}else{
    					int before = getBefore(nextIdx, direction);
    					if(before != thisIdx){
    						//存在能够置换的空白格
    						replace(thisIdx,before);
    					}
    				}
    			}
    		}
    	}

            当中getLast()方法,用于获取当前格子在移动方向的能够移动或者合并的最后一个格子,假设返回值还是当前的格子,则表示不能移动。当中调用的getNext()方法是为了获取当前格子在移动方向的下个格子的位置。

    	/**
    	 * 用于获取移动方向上最后一个空白格之后的位置
    	 * @param index      当前格子的坐标
    	 * @param direction  移动方向
    	 * @return
    	 */
    	public int getLast(int thisIdx, int direction){
    		 int nextIdx = getNext(thisIdx, direction);
    		 if(nextIdx < 0)
    			 return thisIdx;
    		 else{
    			 if(spaceList.contains(nextIdx))
    				 return getLast(nextIdx, direction);
    			 else
    				 return nextIdx;
    		 }		
    	}
            然后是replace(int thisIdx, int nextIdx),这种方法是运行两个格子互换位置。内容主要是对两个格子中的view更换背景图片,然后操作空白格的list和数字格的list:

    	/**
    	 * 该方法用来交换当前格与目标空白格的位置
    	 * @param thisIdx 当前格子的坐标
    	 * @param nextIdx 目标空白格的坐标
    	 */
    	public void replace(int thisIdx, int nextIdx){
    		moved = true;
    		//获取当前格子的view,并将其置成空白格
    		View thisView = gridLayout.getChildAt(thisIdx);
    		ImageView image = (ImageView) thisView.findViewById(R.id.image);
    		image.setBackgroundResource(icons[0]);
    		
    		//获取空白格的view,并将其背景置成当前格的背景
    		View nextView = gridLayout.getChildAt(nextIdx);
    		ImageView nextImage = (ImageView) nextView.findViewById(R.id.image);
    		nextImage.setBackgroundResource(icons[numberList.getNumberByIndex(thisIdx)]);
    		
    		//在空白格列表中。去掉目标格。加上当前格
    		spaceList.remove(spaceList.indexOf(nextIdx));
    		spaceList.add(thisIdx);
    		
    		//在数字格列表中,当前格的坐标置换成目标格的坐标
    		numberList.changeIndex(thisIdx, nextIdx);
    	}


            levelup(int thisIdx, int nextIdx)这种方法是为了实现同样数字格的合并操作。事实上就是将当前的格子置成空白格。将移动方向上下一个格子相应的背景置成下一个背景:

    	/**
    	 * 刚方法用于合并在移动方向上两个同样的格子
    	 * @param thisIdx 当前格子的坐标
    	 * @param nextIdx 目标格子的坐标
    	 */
    	public void levelup(int thisIdx, int nextIdx){
    		
    		//一次移动中,每一个格子最多仅仅能升级一次
    		if(!changeList.contains(nextIdx)){
    			moved = true;
    			//获取当前格子的view,并将其置成空白格
    			View thisView = gridLayout.getChildAt(thisIdx);
    			ImageView image = (ImageView) thisView.findViewById(R.id.image);
    			image.setBackgroundResource(icons[0]);
    			
    			
    			//获取目标格的view,并将其背景置成当前格升级后的背景
    			View nextView = gridLayout.getChildAt(nextIdx);
    			ImageView nextImage = (ImageView) nextView.findViewById(R.id.image);
    			nextImage.setBackgroundResource(icons[numberList.getNumberByIndex(nextIdx)+1]);
    			
    			//在空白格列表中增加当前格
    			spaceList.add(thisIdx);
    			//在数字列中删掉第一个格子
    			numberList.remove(thisIdx);
    			//将数字列表相应的内容升级
    			numberList.levelup(nextIdx);
    			
    			changeList.add(nextIdx);
    		}
    
    	}


    写完这些,基本完毕了基本的推断,可是还有两个问题:1是假设每次滑动没有格子移动(合并),那么就不应该新随机生成格子;2每一个格子仅仅能合并一次。

    为解决这两个问题,我又加了两个变量

    	//用于保存每次操作时。已经升级过的格子
    	List<Integer> changeList = new ArrayList<Integer>();
    	
    	//用于表示本次滑动是否有格子移动过
    	boolean moved = false;


    当中changeList在每次滑动前清空,然后增加本次移动中发生过合并的格子,在每次合并的推断时首先看看要合并的格子是不是在这个list中。假设在的话,说明已经合并过。那么就不运行合并的操作了。

    还有个波尔型的moved变量,这个也是在每次滑动前置为false,假设在本次滑动中,有格子移动或者合并,就置为ture,在滑动的最后。通过这个变量推断是否要随机生产新的格子。


    以下是完整的Activity中的代码:

    package com.example.t2048;
    
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.GestureDetector;
    import android.view.GestureDetector.OnGestureListener;
    import android.view.Menu;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnTouchListener;
    import android.widget.GridLayout;
    import android.widget.ImageView;
    
    public class MainActivity extends Activity {
    	
    	final static int LEFT = -1;
    	final static int RIGHT = 1;
    	final static int UP = -4;
    	final static int DOWN = 4;
    
    	GridLayout gridLayout = null;
    	
    	//用于保存空格的位置
    	List<Integer> spaceList = new ArrayList<Integer>();
    	
    	//全部非空的格子
    	NumberList numberList = new NumberList();
    	
    	//用于保存每次操作时,已经升级过的格子
    	List<Integer> changeList = new ArrayList<Integer>();
    	
    	//用于表示本次滑动是否有格子移动过
    	boolean moved = false;
    	
    	GestureDetector gd = null;
    	
    	/**
    	 * 图标数组
    	 */
    	private final int[] icons = { R.drawable.but_empty, R.drawable.but2,
    			R.drawable.but4, R.drawable.but8, R.drawable.but16,
    			R.drawable.but32, R.drawable.but64, R.drawable.but128,
    			R.drawable.but256, R.drawable.but512, R.drawable.but1024,
    			R.drawable.but2048, R.drawable.but4096 };
    	
    	protected void onCreate(Bundle savedInstanceState) {
    		System.out.println("程序启动");
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		gridLayout = (GridLayout) findViewById(R.id.GridLayout1);
    		
    		init();
    		
    		MygestureDetector mg = new MygestureDetector();
    
    		gd = new GestureDetector(mg);
    		gridLayout.setOnTouchListener(mg);
    		gridLayout.setLongClickable(true);		
    	}
    	
    	//初始化界面
    	public void init(){
    		System.out.println("初始化");
    		
    		//首先在16个各种都填上空白的图片
    		for(int i=0;i<16;i++){
    			View view = View.inflate(this, R.layout.item, null);
    			ImageView image = (ImageView) view.findViewById(R.id.image);
    			
    			image.setBackgroundResource(icons[0]);
    			spaceList.add(i);
    			gridLayout.addView(view);	
    		}
    		
    		//在界面中随机增加两个2或者4
    		addRandomItem();
    		addRandomItem();
    	}
    	
    	//从空格列表中随机获取位置
    	public int getRandomIndex(){
    		Random random = new Random();
    		if(spaceList.size()>0)
    		 return random.nextInt(spaceList.size());
    		else 
    		 return -1;	
    	}
    	
    	//在空白格中随机增加数字2或4
    	public void addRandomItem(){
    		int index = getRandomIndex();
    		if(index!=-1){
    			System.out.println("随机生成数字 位置"+spaceList.get(index));
    			//获取相应坐标所相应的View
    			View view = gridLayout.getChildAt(spaceList.get(index));
    			ImageView image = (ImageView) view.findViewById(R.id.image);
    			//随机生成数字1或2
    			int i = (int) Math.round(Math.random()+1);
    			//将当前格子的图片置换为2或者4
    			image.setBackgroundResource(icons[i]);	
    		
    			//在numList中增加该格子的信息
    			numberList.add(spaceList.get(index), i);
    			
    			//在空白列表中去掉这个格子
    			spaceList.remove(index);
    		
    		}
    	}
    	
    	public class MygestureDetector implements OnGestureListener,OnTouchListener{
    
    		@Override
    		public boolean onTouch(View v, MotionEvent event) {
    			// TODO Auto-generated method stub		
    			return gd.onTouchEvent(event);
    		}
    
    		@Override
    		public boolean onDown(MotionEvent e) {
    			// TODO Auto-generated method stub
    			return false;
    		}
    
    		@Override
    		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    				float velocityY) {
    			
    	        // 參数解释:      
    	        // e1:第1个ACTION_DOWN MotionEvent      
    	        // e2:最后一个ACTION_MOVE MotionEvent      
    	        // velocityX:X轴上的移动速度,像素/秒      
    	        // velocityY:Y轴上的移动速度。像素/秒      
    	        
    	        // 触发条件 :      
    	        // X轴的坐标位移大于FLING_MIN_DISTANCE,且移动速度大于FLING_MIN_VELOCITY个像素/秒    
    			
    			if(e1.getX()-e2.getX()>100){
    				System.out.println("向左");
    				move(LEFT);
    				return true;
    			}else	if(e1.getX()-e2.getX()<-100){
    				System.out.println("向右");
    				move(RIGHT);
    				return true;
    			}else	if(e1.getY()-e2.getY()>100){
    				System.out.println("向上");
    				move(UP);
    				return true;
    			}else	if(e1.getY()-e2.getY()<-00){
    				System.out.println("向下");
    				move(DOWN);
    				return true;
    			}
    			return false;
    		}
    
    		@Override
    		public void onLongPress(MotionEvent e) {
    			// TODO Auto-generated method stub
    			
    		}
    
    		@Override
    		public boolean onScroll(MotionEvent e1, MotionEvent e2,
    				float distanceX, float distanceY) {
    			// TODO Auto-generated method stub
    			return false;
    		}
    
    		@Override
    		public void onShowPress(MotionEvent e) {
    			// TODO Auto-generated method stub
    			
    		}
    
    		@Override
    		public boolean onSingleTapUp(MotionEvent e) {
    			// TODO Auto-generated method stub
    			return false;
    		}
    		
    	}
    	
    	/**
    	 * 用于获取移动方向上下一个格子的位置
    	 * @param index      当前格子的位置
    	 * @param direction  滑动方向
    	 * @return 假设在边界在返回-1
    	 */
    	public int getNext(int index,int direction){
    		
    		int y = index/4;
    		int x = index%4;
    		
    		if(x==3 && direction==RIGHT)	
    			return -1;			
    		if(x==0 && direction==LEFT)
    			return -1;
    		if(y==0 && direction==UP)
    			return -1;
    		if(y==3 && direction==DOWN)
    			return -1;	
    		return index+direction;	
    	}
    	
    	/**
    	 * 用于获取移动方向上前一个格子的位置
    	 * @param index      当前格子的位置
    	 * @param direction  滑动方向
    	 * @return 假设在边界在返回-1
    	 */
    	public int getBefore(int index,int direction){
    		
    		int y = index/4;
    		int x = index%4;
    		
    		if(x==0 && direction==RIGHT)	
    			return -1;			
    		if(x==3 && direction==LEFT)
    			return -1;
    		if(y==3 && direction==UP)
    			return -1;
    		if(y==0 && direction==DOWN)
    			return -1;	
    		return index-direction;	
    	}
    	
    
    	/**
    	 * 该方法用来交换当前格与目标空白格的位置
    	 * @param thisIdx 当前格子的坐标
    	 * @param nextIdx 目标空白格的坐标
    	 */
    	public void replace(int thisIdx, int nextIdx){
    		moved = true;
    		//获取当前格子的view,并将其置成空白格
    		View thisView = gridLayout.getChildAt(thisIdx);
    		ImageView image = (ImageView) thisView.findViewById(R.id.image);
    		image.setBackgroundResource(icons[0]);
    		
    		//获取空白格的view,并将其背景置成当前格的背景
    		View nextView = gridLayout.getChildAt(nextIdx);
    		ImageView nextImage = (ImageView) nextView.findViewById(R.id.image);
    		nextImage.setBackgroundResource(icons[numberList.getNumberByIndex(thisIdx)]);
    		
    		//在空白格列表中,去掉目标格。加上当前格
    		spaceList.remove(spaceList.indexOf(nextIdx));
    		spaceList.add(thisIdx);
    		
    		//在数字格列表中,当前格的坐标置换成目标格的坐标
    		numberList.changeIndex(thisIdx, nextIdx);
    	}
    	
    	/**
    	 * 刚方法用于合并在移动方向上两个同样的格子
    	 * @param thisIdx 当前格子的坐标
    	 * @param nextIdx 目标格子的坐标
    	 */
    	public void levelup(int thisIdx, int nextIdx){
    		
    		//一次移动中。每一个格子最多仅仅能升级一次
    		if(!changeList.contains(nextIdx)){
    			moved = true;
    			//获取当前格子的view,并将其置成空白格
    			View thisView = gridLayout.getChildAt(thisIdx);
    			ImageView image = (ImageView) thisView.findViewById(R.id.image);
    			image.setBackgroundResource(icons[0]);
    			
    			
    			//获取目标格的view,并将其背景置成当前格升级后的背景
    			View nextView = gridLayout.getChildAt(nextIdx);
    			ImageView nextImage = (ImageView) nextView.findViewById(R.id.image);
    			nextImage.setBackgroundResource(icons[numberList.getNumberByIndex(nextIdx)+1]);
    			
    			//在空白格列表中增加当前格
    			spaceList.add(thisIdx);
    			//在数字列中删掉第一个格子
    			numberList.remove(thisIdx);
    			//将数字列表相应的内容升级
    			numberList.levelup(nextIdx);
    			
    			changeList.add(nextIdx);
    		}
    
    	}
    	
    	/**
    	 * 该方法为不同的滑动方向。运行不同的遍历顺序
    	 * @param direction 滑动方向
    	 */
    	public void move(int direction){
    		
    		moved = false;
    		
    		changeList.clear();
    		
    		numberList.printLog();
    		
    		switch(direction){
    		case RIGHT:
    			for(int y=0;y<4;y++){
    				for(int x=2;x>=0;x--){
    					int thisIdx = 4*y +x;
    					Change(thisIdx,direction);
    				}
    			}
    			break;
    		case LEFT:
    			for(int y=0;y<4;y++){
    				for(int x=1;x<=3;x++){
    					int thisIdx = 4*y +x;
    					Change(thisIdx,direction);
    				}
    			}
    			break;
    		case UP:
    			for(int x=0;x<4;x++){
    				for(int y=1;y<=3;y++){
    					int thisIdx = 4*y +x;
    					Change(thisIdx,direction);
    				}
    			}
    			break;	
    		case DOWN:
    			for(int x=0;x<4;x++){
    				for(int y=2;y>=0;y--){
    					int thisIdx = 4*y +x;
    					Change(thisIdx,direction);
    				}
    			}
    			break;
    		}
    		
    		//假设本次滑动有格子移动过,则随机填充新的格子
    		if(moved)
    			addRandomItem();
    
    	}
    	
    	/**
    	 * 该方法。为每一个符合条件的格子运行变动的操作,如置换,升级等
    	 * @param thisIdx     当前格子的坐标
    	 * @param direction   滑动方向
    	 */
    	public void Change(int thisIdx,int direction){
    		if(numberList.contains(thisIdx)){
    						
    			int nextIdx = getLast(thisIdx, direction);
    			if(nextIdx == thisIdx){
    				//不能移动
    				return;
    			}else if(spaceList.contains(nextIdx)){
    				//存在能够置换的空白格
    				replace(thisIdx,nextIdx);
    			}else{				
    				if(numberList.getNumberByIndex(thisIdx) == numberList.getNumberByIndex(nextIdx)){
    					//能够合并
    					levelup(thisIdx, nextIdx);
    				}else{
    					int before = getBefore(nextIdx, direction);
    					if(before != thisIdx){
    						//存在能够置换的空白格
    						replace(thisIdx,before);
    					}
    				}
    			}
    		}
    	}
    	
    	/**
    	 * 用于获取移动方向上最后一个空白格之后的位置
    	 * @param index      当前格子的坐标
    	 * @param direction  移动方向
    	 * @return
    	 */
    	public int getLast(int thisIdx, int direction){
    		 int nextIdx = getNext(thisIdx, direction);
    		 if(nextIdx < 0)
    			 return thisIdx;
    		 else{
    			 if(spaceList.contains(nextIdx))
    				 return getLast(nextIdx, direction);
    			 else
    				 return nextIdx;
    		 }		
    	}
    	
    	public boolean onCreateOptionsMenu(Menu menu) {
    		// Inflate the menu; this adds items to the action bar if it is present.
    		getMenuInflater().inflate(R.menu.main, menu);
    		return true;
    	}
    
    }
    


             写到这里,做为我学习Android以来。第一个自己写的程序已经完毕一半了。

    逻辑推断这部分写的时候,还是费了一点时间,由于总有一些情况没有考虑进来,到如今基本上已经实现了。可是也反应出来一个非常重要的问题。那就是自己在数据结构和算法方面还是非常薄弱。整个读一下自己写的代码,为了完毕对各种情况的推断。整个代码看起来十分冗余。并且效率之低就更不用说了。再看看别人写的代码。感觉自己在开发方面还是有非常长的路要走的。

            接下来的时间,我会利用工作之余的时间不断去完好这个程序,并尽可能的去优化。

    大家共勉吧!



    代码写成这样,我也不藏拙了,我把代码打包上传了,须要的朋友能够下载,也希望大家多多指正

       下载地址http://download.csdn.net/detail/johnsonwce/7269315

  • 相关阅读:
    Python内置函数(49)——isinstance
    Python内置函数(48)——__import__
    Python内置函数(47)——vars
    Python内置函数(46)——format
    Python内置函数(45)——ascii
    Python内置函数(44)——len
    Python内置函数(43)——type
    Python内置函数(42)——hash
    Python内置函数(41)——id
    Linux下redis常用命令
  • 原文地址:https://www.cnblogs.com/yfceshi/p/7293851.html
Copyright © 2011-2022 走看看