標籤: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或者4addRandomItem();addRandomItem();}//從空格列表中隨機擷取位置public int getRandomIndex(){Random random = new Random();if(spaceList.size()>0) return random.nextInt(spaceList.size());else return -1;}//在空白欄框中隨機加入數字2或4public void addRandomItem(){int index = getRandomIndex();if(index!=-1){System.out.println("隨機產生數字 位置"+spaceList.get(index));//擷取對應座標所對應的ViewView view = gridLayout.getChildAt(spaceList.get(index));ImageView image = (ImageView) view.findViewById(R.id.image);//隨機產生數字1或2int i = (int) Math.round(Math.random()+1);//將當前格子的圖片置換為2或者4image.setBackgroundResource(icons[i]);//在numList中加入該格子的資訊numberList.add(spaceList.get(index), i);//在空白列表中去掉這個格子spaceList.remove(index);}}public class MygestureDetector implements OnGestureListener,OnTouchListener{@Overridepublic boolean onTouch(View v, MotionEvent event) {// TODO Auto-generated method stubreturn gd.onTouchEvent(event);}@Overridepublic boolean onDown(MotionEvent e) {// TODO Auto-generated method stubreturn false;}@Overridepublic 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;}elseif(e1.getX()-e2.getX()<-100){System.out.println("向右");move(RIGHT);return true;}elseif(e1.getY()-e2.getY()>100){System.out.println("向上");move(UP);return true;}elseif(e1.getY()-e2.getY()<-00){System.out.println("向下");move(DOWN);return true;}return false;}@Overridepublic void onLongPress(MotionEvent e) {// TODO Auto-generated method stub}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) {// TODO Auto-generated method stubreturn false;}@Overridepublic void onShowPress(MotionEvent e) {// TODO Auto-generated method stub}@Overridepublic boolean onSingleTapUp(MotionEvent e) {// TODO Auto-generated method stubreturn 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
從零開始開發Android版2048 (三)邏輯判斷