Android 2048 極簡之旅

開始!

2048的遊戲,說幹就幹。

相比之前用swing做的五子棋,安卓上的2048真是難度增加了很多;做完會感嘆一句——真是適合新手練習編程的好東西T T

 

分析一下游戲系統的組成。在本遊戲中,大致可以列爲

1.遊戲體

2.計分牌

3.操作與提示

4.視覺優化

 

隨着代碼行數的增加,我也漸漸感受到了編程的最佳順序:核心功能——次要功能——畫面效果等。對於我等新手來說,編程的順序像是粉刷,是一遍一遍刷出來的。

開發遊戲也是一樣,先實現最基本的規則,然後再對其進行修正。按照這樣的編程思想,我們一步步開始進行2048的開發。

 

2048的規則是啥?

首先,遊戲隨機在兩個方塊內生成隨機的2或4,然後你就往某個方向滑屏幕吧,見着空地它就划過去了,一直到底。假如滑的方向有連續兩個相同的數字,他們還會合並,順着滑的方向知道底端。在此之後,還會在空餘的地方隨機生成兩個2或4,讓遊戲得以繼續。

 

一:遊戲體規則

 

1.對於每一行/列來說,每滑動一次到下次滑動之前的這段判斷範圍內,遊戲遵循以下原則:

 

(一):能動則動,不撞南牆不回頭原則

 

以向右滑爲例,不管是2000,0200,還是0020,每個2的右邊都有至少一個空餘的方塊,此時向右滑動,2遇到邊界(牆)才停止,故一律變爲0002.而0002這種情況本身則無法再滑動;如果是2004,0204,每個2右邊仍然有至少一個空格,此時的牆是4,自然一律變爲0024,,而0024本身這種情況則不能向此方向移動。

 

(二):從滑動方向反着看,來同類則吞併(合併)原則

 

仍然以向右滑爲例,假設滑動之前的情況是0202,那麼從滑動的反方向來看,第一個是2,稱爲大2,第三個是2,稱爲小2,這時的小2按照原則一向滑動方向滑動,此時的大2則會將其吞併,變爲0004.這裏“反着看”是爲了方便分析。類似的情況,比如4040,按照原則一,兩個4都向右滑動,可以看做是右邊發4先滑動到底,變爲4004,然後按照本條原則,左邊的4向右滑動,被右邊的4吞併,變爲0008.

 

(三):原則二之上的原則:每次最多隻合併兩個數原則

 

仍然以向右滑爲例,假設滑動之前的情況是2022,那麼這時可以看做是第三個2向右滑動被最後一個2吞併變成2004,然後第一個2向右滑動到這時的牆“4”之前,變爲0024,而不是0006.

同樣的,如果滑動之前的情況是2222,那麼可以看做是第三個2被最後一個2吞併變爲2204,然後第二個2向右滑變爲2024,再然後第一個2向右滑被現在的第三個位置的2吞併變爲0044.此時兩個4要不要合併?此時引出下一個原則:

 

(四):合併一次得到的結果不能再(被)合併原則,即向右滑動的2222只能變爲0044而非0008.

 

2.對於遊戲過程來說,每次滑動之後,如果發生了數字的移動或合併(合併了一定已經移動過),我們稱其爲“合格的滑動”,也就是遊戲進行了一回合。而在類似下圖的情況時,如果向下滑動,是不能發生數字的移動或合併的,按照遊戲規則,不能是一擦合格的移動。這裏的移動合格與否會決定是否在隨機的空格位置產生一個新的2或4.如果移動合格,則在一個隨機的空白位置產生一個2或者4.否則不應有任何變化。

 

 

二:記分牌規則

 

按照遊戲規則,每發生一次合併,分數將增加,增加的值等於合併後的值,並且只要分數增加,就顯示出來。

當按下“重新開始”按鈕時,把當前分數與之前的最高分比較,更新最高分,然後將當前分數置零,遊戲主體界面只顯示重新生成的2或4。

 

三:操作與提示

 

 

 

當遊戲進行到一定程度,無法繼續產生合格的滑動,遊戲結束,彈出提示窗口,上面有“重新開始”的按鈕,按下之後重新開始遊戲,與直接按下重新開始按鈕效果相同。

 

四:視覺優化

 

第一步實現遊戲主體。這裏採用了自定義組件的方法,將遊戲體當做一個組件,自定義這個組件的內容,直接放在xml文件中,再與Activity等聯繫起來即可。


既然要自定義組件自然要抱View的大腿,繼承之後發現有三個構造方法,add哪個呢?注意啦,context可以理解爲Activity,(順着api可以看到它其實是Activity的父類,哦不,爺爺類,爺爺的爺爺類)AttributeSet可以理解爲在Layout中向圖形編程界面拖動組件時的參數,int defStyle則是默認的組件試圖格式,那麼我們用其中的第二個就可以了。


注意,僅僅重寫構造方法是不夠的,還需重寫onDraw方法。如何理解?我的理解方法是,Game2048blog是繼承了View的孩子,構造方法決定了他的性格,paint即是它思考問題的方法,是內在的,是準備的,是選擇好畫筆,而onDraw則是他在出生時的樣子,用到的canvas是母親的子宮,“磨合”了他出生時的外貌,是外在的,是動詞,是真正實施了;二者缺一不可。有沒有好理解一點呢?

 

1.繼承View

 

 

來一個萌萌的自定義組件……

 

這時就可以在Layout裏看到它的身影啦。可以拖上去試試~

 

2.重寫構造方法、onDraw方法

我們需要畫出這樣的邊框,寫出這樣的數字

所以我們要畫出一個大大的圓角矩形,和16個小圓角矩形,於是,我們加上了下面幾行代碼對paint進行設置:

 

注意空心矩形與實心的區別,在於setStyle。

這時重寫onDraw方法,用canvas畫出矩形。這裏想達到的效果是畫出的圖形能與控件適配。

 

這裏強調畫圓角矩形的方法。實例化RectF對象r1,幾個參數的含義如註釋所示,均爲矩形邊緣距離組件邊緣的距離。

畫完了輪廓畫數字,不過這時候能做的最多的也就是設置下字體的格式啦。

 

這裏一個小tip:如何設置字體居中?

 

在重寫onDraw方法時,需要對字體進行設置

paint.setTextAlign(Paint.Align.CENTER);//使座標處於字體中間而不是左下角,便於水平調整座標

paint.setTextSize(108);//設置大小

在需要畫數字時,則要再計算字體高度,找準縱座標與baseline之間的關係 即

 

FontMetrics fontMetrics =paint.getFontMetrics();

// 計算文字高度

float fontHeight =fontMetrics.bottom - fontMetrics.top;

 

 

 

遊戲要求我們在剛剛進入遊戲時就要隨機顯示兩個2或4,也就是需要在onDraw裏畫出數字。爲了更有條理,我們把畫數字的方法單獨拿出來,也就是

 

 

這裏的array數組用來儲存每個空格應該顯示的值(0不顯示),應定義爲全局變量。

 

3.滑動事件處理(燒腦)方法

 

處理事件要有監聽器,事件處理方法。

這裏要解決 個問題:

1.向哪個方向滑動屏幕?

2.滑了之後會發生什麼?

 

1.找得着北

Android裏有關於手勢的Gesture接口,我們也可以自己根據手指起落點的座標寫代碼判斷方向。方法多種多樣。

大坑警示:一定要用switch case判斷event.getAction()中的MotionEvent.ACTION_UP和MotionEvent.ACTION_DOWN,一定不要用if else if,否則會出現奇葩的結果!原因是UP之前一定已經進行了Down,所以用if  else if 會出現混亂的邏輯,而switchcase中的break則保證了監聽的獨立性。

 

2.遊戲開始!

此即2048的精髓所在,也是通篇最難的地方。

根據上文闡述的規則,我們要通過一定的算法實現這些原則。

 

首先實現原則一:不撞南牆不回頭原則。怎樣把2000變成0002?怎樣把4020變爲0042?我想,最直接的方式就是“擇zhai”出來。我們向右滑動,向右撞牆,其實是把原來的數組忽略了0的存在,然後從右向左排序。所以基本思路就是,挑出所有不是0的值,並按順序從頭開始賦給另一個數組,然後再從右向左賦回來。比如4020向右滑動,就從右向左依次挑出不是0的值即24,賦給tempArray[],則tempArray[]成爲2400,然後從右向左賦給array,array變爲0042.

 

// 逐行

for (int i = 0; i < 4;i++) {

intnoSpaceCount = 0;

//中間數組

int[]tempArray = new int[4];

//從右向左逐個

for(int j = 3; j >= 0; j--) {

//把所有不是0 的複製到中間數組

if(array[i][j] != 0) {

tempArray[noSpaceCount]= array[i][j];

noSpaceCount++;

}

}

 

//開始返還

for(int m = 0; m < 4; m++) {

array[i][m] =tempArray[m];

}

}

 

 

原則二:即原則一加上合併

步驟的關鍵在於反向賦值是採用兩套循環,tempArray一套,array一套。仍然是從右向左依次賦值,但是每次(第一次除外)tempArray都要檢查自己是不是與上一個值相同。如果不同,照常賦值,且兩個循環控制變量都發生變化;如果相同(此時上一個tempArray已經賦給了對應的array),則修改上一個array爲它的二倍,而自身變爲-1,使它下一個不可能與它相同,遵守原則三;此時array的循環控制變量不變,使其落後temp一個步伐,而tempArray的循環控制變量始終變化。比如,滑動前是2220,向右滑動,tempArray變爲2220;然後開始反向賦回:假設array[i][n]的循環變量爲n=3,temp的循環變量爲m=0。開始處理事件,首個tempArray不檢查直接賦值,array[i][3]=tempArray[0]=2,n-1變爲2,m+1變爲1;tempArray[1]=2,與上一個tempArray相同,這時將上一個arraay變爲2倍,即array[i][3]變爲4,而自己即tempArray變爲-1,n不變還是2,使array落後1個步伐,m照常加1變爲2;tempArray[2]=2,上一個tempArray即tempArray[1]=-1與之不等,故將其賦給array[n]即array[2]變爲2。目前,array變爲0224.tempArray是2,-1,2,0,咦,tempArray不夠用了!

 

// 逐行

for (int i = 0; i < 4;i++) {

int n = 3;

intnoSpaceCount = 0;

//中間數組

int[]tempArray = new int[4];

//從右向左逐個

for(int j = 3; j >= 0; j--) {

//把所有不是0 的複製到中間數組

if(array[i][j] != 0) {

tempArray[noSpaceCount]= array[i][j];

noSpaceCount++;

}

}

//開始返還

for (int m = 0; m < 4;m++) {

if (m== 0) {// 第一個直接複製,array計數增長

array[i][n]= tempArray[m];

n--;

} elseif (m > 0 && tempArray[m] != tempArray[m - 1]) {//從第二個開始,不等於0且與前一個不同的,每一個都賦給array,n--

array[i][n]= tempArray[m];

n--;

} elseif (m > 0 &&tempArray[m] == tempArray[m - 1]) {//從第二個開始,不等於0且與前一個相同的,把前一array*2,自己變成-1,n不變,分數增加

array[i][n+ 1] = array[i][n + 1] + array[i][n + 1];

tempArray[m]= -1;

}

}

 

雖然tempArray不夠用了,但是可以預料到之後的tempArray也應該看做是0.或者說,array總共有4位,被賦回來n位,那餘下的幾位都應被置零。在本例中,應把array[0]到array[n]全部置零。即

// 把餘下的歸零

for (int k = 0; k <=n; k++) {

array[i][k]= 0;

}

 

還有一種情況,如果tempArray裏有連續2個0呢?如果tempArray是0000,那array豈不變成了-1,-1,0,0?所以,從第二個tempArray開始,不僅要判斷是否與上一個相同,還要加上一個tempArray不是0的前提。也就是說,只要tempArray是0,本輪判斷終止,載結合k值置零。

即之前的判斷條件應修改爲

if (m == 0)

 else if (m > 0 && tempArray[m] !=0&& tempArray[m] != tempArray[m - 1])

else if (m > 0&& tempArray[m] != 0&& tempArray[m] == tempArray[m - 1])

 

接下來要隨機在空地產生1個2或4.注意!前提是產生了“合格的滑動”纔可產生新數字。所以需要進行一個判斷:是否進行了合格的滑動?

我的思路是,處理完某一行/列之後,檢查此行/列是否與處理之前相同,如果完全相同,那麼計數+1.檢查完4行/列之後,如果計數仍然爲0,那麼此次滑動不“合格”。否則就是合格的。

如果是合格的滑動,那麼隨機選擇空餘的空格產生新的2或4.

 

 

目前爲止,滑動判斷告一段落,上述代碼可稍加整理爲

 

// 逐行

for (int i = 0; i < 4;i++) {

intnoSpaceCount = 0;

int n= 3;

//中間數組

int[]tempArray = new int[4];

//從右向左逐個

for(int j = 3; j >= 0; j--) {

 

//把所有不是0 的複製到中間數組

if(array[i][j] != 0) {

tempArray[noSpaceCount]= array[i][j];

noSpaceCount++;

}

}

//開始返還

for(int m = 0; m < 4; m++) {

if (m== 0) {// 第一個直接複製,array計數變化

array[i][n]= tempArray[m];

n--;

 

} else if (m > 0 && tempArray[m] != 0&&tempArray[m] != tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個不同的,每一個都賦給array,n--

array[i][n]= tempArray[m];

n--;

} else if (m > 0 && tempArray[m] != 0&&tempArray[m] == tempArray[m - 1]) {//從第二個開始,不等於0且與前一個相同的,把前一array*2,自己變成-1,n不增加,分數增加

array[i][n+ 1] = array[i][n + 1] + array[i][n + 1];

tempArray[m]= -1;

}

}

for(int j = 0, f = 3; j < 4; j++, f--) {

if(tempArray[f] != array[i][j]) {

zeroCountRight++;

}

}

//把餘下的歸零

for(int k = 0; k <= n; k++) {

array[i][k]= 0;

}


if (zeroCountRight >0) {

//在空處隨機生成一個2或4

randomNew();

}

 

注意,還沒有結束。上邊這一串代碼的作用無非是給Aarray賦了新的值,但還沒有畫數字!

此時需要調用

 

this.postInvalidate();

 

這個方法可以理解爲重新畫這個view,或者可以理解爲調用onDraw方法。

 

其他三個方向的基本原理是相同的,但是在賦值時一定要搞清順序與方向。

 

代碼至此,已經可以運行了,來回滑滑,我已經聞到了2048的味道~~但是這哪能滿足。

 

二:記分牌

 

其實就是個顯示當前分數和最高分數的兩行字啦。

 

第一步,在Layout中拖(碼)兩個TextView出來,設置內容和id

第二步,在剛剛寫的代碼中加上各種布丁~~

 

布丁1:初始化score爲0.根據遊戲規則,每當合併時,score增加合併值。也就是隻要發生了

else if (m > 0&& tempArray[m] != 0&& tempArray[m] == tempArray[m - 1])

score+=tempArray[m - 1]

 

分數反應到哪裏?應當用activity的findViewById通過id找到表示當前最高分的組件,調用settext方法使分數改變。

 

何時改變?每滑動一次,改變了分數的值,在刷新時得以向人展現,故將score設爲全局變量,在onDraw中寫入:

 

TextView scoreView =(TextView) ma.findViewById(R.id.score);

scoreView.setText("當前分數:"+ score);

 

是的,這裏的ma即是傳過來的Activity。

 

怎麼傳?

 

注意到,在view的構造方法中即出現了Context類型參數,在本程序中即是Activity——

也就是說,自定義組件時已經出現了一個披着Context外衣的Activity~~

 

那麼,只要聲明一個Activity類變量ma,自定義一個getActivity方法即可獲取Activity!

 

private Activity ma;

 

public voidsetActivity(Context context) {

this.ma= (Activity) context;

}

完事~

 

接下來是最高分的更新。場景應是按下了“重新開始”按鈕,產生三個驚天動地的變化:

 

1.score變成0了!

2.最高分更新了!

3.下面的數字們消失了,又生成了新的兩個2或4!

 

 

於是我們又添加了一個Button……

 

 

屏幕剪輯的捕獲時間: 2016/1/17 星期日 1:40

 

又邀請到了ButListener……

 

 

屏幕剪輯的捕獲時間: 2016/1/17 星期日 1:42

 

挑瘦肉吃!

按下按鍵怎麼辦?

 

 應該是game.setScore(0)(這意味着要把Context傳遞到ButListener,還要在自定義2048組件裏設置set方法)

 

也就是

 Game2048 game =(Game2048)ma.findViewById(R.id.game1);

 game.setScore(0);//

 for(int i=0;i<4;i++){

  for(int j=0;j<4;j++){

  game.setArray(i, j, 0);

  game.postInvalidate();

}

}

還有鋪墊

 


 

 

 

還有最高分的事情。首先要從自定義組件中得到最高分,然後還要在按鈕監聽器中重置最高分

 

 TextView maxS =(TextView)ma.findViewById(R.id.maxScore);

 maxS.setText("最高分:"+maxScore);

 

大量用到了set方法和get方法!還有特殊的Context!即setContext(){this.context =context}和Activity ma = (Activity)context的配合使用。寫在一起也挺好。

 

 

三.操作與提示

 

相信我,這遊戲總有結束的那一刻。

 

 

真的進行不下去了!此時總得給點反應把!?

 

1.這是什麼情況?

 

答:這是“任何行/列都沒有連續兩個相同的數字&&格子滿了”的情況。採集情況的方法是,定義一個checkCount參數統計空格數量,定義sameCount參數統計有連續兩個數字相同的情況的數量。都是0,那就是真輸了。

 

 

PS :不是全滿了就無藥可救了,比如

 

 

就還能向右滑

 

也不是任意行/列都沒有連續的兩個數字就無藥可救了,比如

 

 

顯然還能滑。

 

2.這種情況怎麼辦?

 

彈出窗口,提示你輸了,出現“重新開始”按鈕,按下之後真的重新開始了。

 

如何彈窗?

 

這裏的彈窗是簡單的“窗體”+“標題”+“內容”+“按鈕”的彈窗。

 

new AlertDialog.Builder(Context context)

.setTitle(CharSequence title)//標題

.setMessage(CharSequence message)//內容

.setPositiveButton(CharSequence text,OnClickListener listener())//按鈕內容+監聽器

.show();

 

這裏的監聽事件處理方法與直接點擊 重新開始時一樣的。

 

 

 

終於完成啦,盡情玩(zhuang)耍(bi)吧~

 

 

 附源碼:



package com.example.android2048;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.widget.Button;
import android.widget.TextView;
 
public class MainActivity extends Activity {
 
int maxScore;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButListener bl= new ButListener(maxScore,this);
Button but = (Button)this.findViewById(R.id.butReStart);
but.setOnClickListener(bl);
}
 
 
 
@Override
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;
}
 
}

*************************************************************************************


package com.example.android2048;
 
import java.util.Random;
 
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
 
public class Game2048 extends View {
private int[][] array = new int[4][4];
private Paint paint;
private int score = 0;
private int maxScore = 0;
private Activity ma;
// 獲取按下與擡起的座標以便判斷方向
 
private int d = 0;// 方向變量
 
private float x1 = 0, y1 = 0;// 座標爲float型
private float x2 = 0, y2 = 0;
 
/**
 * 從主方法中獲取score的方法
 * 
 * @return
 */
public int getScore() {
return score;
}
 
public void setActivity(Context context) {
this.ma = (Activity) context;
}
 
public int getMaxScore() {
return maxScore;
}
 
public void setArray(int i, int j, int a) {
this.array[i][j] = a;
}
 
public void setScore(int score) {
this.score = score;
}
 
/**
 * 自定義組件時用到的構造方法
 * 
 * @param context
 * @param attrs
 */
public Game2048(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
paint = new Paint();
paint.setColor(Color.BLACK);// 設置顏色,此步驟不可省略
// 設置爲空心
paint.setStyle(Paint.Style.STROKE);
// 設置將文本的x焦點對準中心而不是左邊
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(108);
 
// 把Activity傳過來
setActivity(context);
}
 
/**
 * 重寫onDraw方法,在繪製組件的時候畫圖
 */
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
 
this.getWidth();
this.getHeight();
 
RectF r1 = new RectF();
r1.top = 0;// 頂端到上沿的距離
r1.bottom = this.getHeight();// 底端到上沿的距離
r1.left = 0;// 左端到左邊的距離
r1.right = this.getWidth();// 右端到左邊的距離
 
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
RectF r2 = new RectF();
r2.top = this.getHeight() / 45 + i * this.getHeight()
* (10 + 1) / 45;
r2.left = this.getWidth() / 45 + j * this.getWidth() * (10 + 1)
/ 45;
r2.right = this.getWidth() * (10 + 1) / 45 + j
* this.getWidth() * (10 + 1) / 45;
r2.bottom = this.getHeight() * (10 + 1) / 45 + i
* this.getHeight() * (10 + 1) / 45;
canvas.drawRoundRect(r2, 10, 10, paint);
}
}
RectF r2 = new RectF();
r2.top = 0;// 頂端到上沿的距離
r2.bottom = this.getHeight();// 底端到上沿的距離
r2.left = 0;// 左端到左邊的距離
r2.right = this.getWidth();// 右端到左邊的距離
 
canvas.drawRoundRect(r1, 10, 10, paint);// 中間的是圓角矩形的半徑
 
// 只有在array均爲0時才執行隨機24
int count1 = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (array[i][j] == 0) {
count1++;
}
}
}
if (count1 == 16) {
RandomData(canvas);
}
// 畫數字
drawNumber(canvas);
// 傳值
TextView scoreView = (TextView) ma.findViewById(R.id.score);
scoreView.setText("當前分數:" + score);
 
}
 
/**
 * 畫全部數字的方法
 * 
 * @param canvas
 */
public void drawNumber(Canvas canvas) {
// 使文字垂直居中的準備
FontMetrics fontMetrics = paint.getFontMetrics();
// 計算文字高度
float fontHeight = fontMetrics.bottom - fontMetrics.top;
 
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (array[i][j] != 0) {
canvas.drawText(
array[i][j] + "",
this.getWidth() * (1 + 5) / 45 + j
* this.getWidth() * (10 + 1) / 45,
this.getHeight() * (1 + 5) / 45 + i
* this.getHeight() * (10 + 1) / 45
+ fontHeight / 4, paint);
}
 
}
}
 
}
 
/**
 * 在隨機的兩個地方生成隨機的2或4
 */
public void RandomData(Canvas canvas) {
Random random = new Random();
 
int r1;
int c1;
int r2;
int c2;
// 防止重複座標
r1 = random.nextInt(4);
c1 = random.nextInt(4);
do {
r2 = random.nextInt(4);
c2 = random.nextInt(4);
} while (r1 == c1 && r2 == c2);
 
// 將數據存儲
array[r1][c1] = random.nextInt(2) * 2 + 2;
array[r2][c2] = random.nextInt(2) * 2 + 2;
}
 
/**
 * 手指滑動時的遊戲處理方法
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
 
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
 
x1 = event.getX();
y1 = event.getY();
 
System.out.println(">>>" + x1);
break;
case MotionEvent.ACTION_UP:
x2 = event.getX();
y2 = event.getY();
double z = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));// 距離
double s = (y2 - y1) / z;// 正弦值
 
if (y2 - y1 < 0 && (-1) <= s && s <= (-(Math.sqrt(2) / 2))) {
d = 1;// 向上
} else if (y2 - y1 > 0 && (Math.sqrt(2) / 2) <= s && s <= 1) {
d = 2;// 向下
} else if (x2 - x1 < 0 && (-Math.sqrt(2) / 2) <= s
&& s <= (Math.sqrt(2) / 2)) {
d = 3;// 向左
} else if (x2 - x1 > 0 && (-Math.sqrt(2) / 2) <= s
&& s <= (Math.sqrt(2) / 2)) {
d = 4;// 向右
}
 
 
 
switch (d) {
// 向上
case 1:
System.out.println("上");
int zeroCountUp = 0;
// 逐列
for (int j = 0; j < 4; j++) {
int noSpaceCount = 0;
int n = 0;
// 中間數組
int[] tempArray = new int[4];
// 逐個
for (int i = 0; i < 4; i++) {
// 把所有不是0 的複製到中間數組
if (array[i][j] != 0) {
tempArray[noSpaceCount] = array[i][j];
noSpaceCount++;
}
}
 
// 開始返還
for (int m = 0; m < 4; m++) {
if (m == 0) {// 第一個直接複製,array計數增長
array[n][j] = tempArray[m];
n++;
 
} else if (m > 0 && tempArray[m] != 0 && tempArray[m] != tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個不同的,每一個都賦給array,n++
array[n][j] = tempArray[m];
n++;
} else if (m > 0 && tempArray[m] != 0&& tempArray[m] == tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個相同的,把前一array*2,自己變成-1,n不增加,分數增加
array[n - 1][j] = array[n - 1][j] + array[n - 1][j];
tempArray[m] = -1;
score += array[n - 1][j];
}
}
// 檢查是否有移動空間
for (int i = 0; i < 4; i++) {
if (tempArray[i] != array[i][j]) {
zeroCountUp++;
}
}
// 把餘下的歸零
for (int k = n; k < 4; k++) {
array[k][j] = 0;
}
}
if (zeroCountUp > 0) {
// 在空處隨機生成一個2或4
randomNew();
}
if (score > maxScore) {
maxScore = score;
}
// 判斷輸贏
judgeLose();
// 刷新界面,畫新數字
this.postInvalidate();
 
break;
 
case 2:// 向下
System.out.println("下");
int zeroCountDown = 0;
 
// 逐列
for (int j = 0; j < 4; j++) {
int noSpaceCount = 0;
int n = 3;
// 中間數組
int[] tempArray = new int[4];
// 從下向上逐個
for (int i = 3; i >= 0; i--) {
 
// 把所有不是0 的複製到中間數組
if (array[i][j] != 0) {
tempArray[noSpaceCount] = array[i][j];
noSpaceCount++;
}
}
 
// 開始返還
for (int m = 0; m < 4; m++) {
if (m == 0) {// 第一個直接複製,array計數增長
array[n][j] = tempArray[m];
n--;
 
} else if (m > 0 && tempArray[m] != 0
&& tempArray[m] != tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個不同的,每一個都賦給array,n--
array[n][j] = tempArray[m];
n--;
} else if (m > 0 && tempArray[m] != 0
&& tempArray[m] == tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個相同的,把前一array*2,自己變成-1,n不增加,分數增加
array[n + 1][j] = array[n + 1][j] + array[n + 1][j];
tempArray[m] = -1;
score += array[n + 1][j];
}
}
for (int i = 3, f = 0; i >= 0; i--, f++) {
if (tempArray[i] != array[f][j]) {
zeroCountDown++;
}
}
// 把餘下的歸零
for (int k = 0; k <= n; k++) {
array[k][j] = 0;
}
}
if (zeroCountDown > 0) {
// 在空處隨機生成一個2或4
randomNew();
}
 
if (score > maxScore) {
maxScore = score;
}
// 刷新界面,畫新數字
this.postInvalidate();
// 判斷輸贏
judgeLose();
 
break;
 
case 3:// 向左
System.out.println("左");
int zeroCountLeft = 0;
 
// 逐行
for (int i = 0; i < 4; i++) {
int noSpaceCount = 0;
int n = 0;
// 中間數組
int[] tempArray = new int[4];
// 逐個
for (int j = 0; j < 4; j++) {
// 把所有不是0 的複製到中間數組
if (array[i][j] != 0) {
tempArray[noSpaceCount] = array[i][j];
noSpaceCount++;
}
}
 
// 開始返還
for (int m = 0; m < 4; m++) {
if (m == 0) {// 第一個直接複製,array計數增長
array[i][n] = tempArray[m];
n++;
 
} else if (m > 0 && tempArray[m] != 0
&& tempArray[m] != tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個不同的,每一個都賦給array,n++
array[i][n] = tempArray[m];
n++;
} else if (m > 0 && tempArray[m] != 0
&& tempArray[m] == tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個相同的,把前一array*2,自己變成-1,n不增加,分數增加
array[i][n - 1] = array[i][n - 1] + array[i][n - 1];
tempArray[m] = -1;
score += array[i][n - 1];
}
}
for (int j = 0; j < 4; j++) {
if (tempArray[j] != array[i][j]) {
zeroCountLeft++;
System.out.println(zeroCountLeft);
}
}
// 把餘下的歸零
for (int k = n; k < 4; k++) {
array[i][k] = 0;
}
}
if (zeroCountLeft > 0) {
// 在空處隨機生成一個2或4
randomNew();
}
 
if (score > maxScore) {
maxScore = score;
}
// 刷新界面,畫新數字
this.postInvalidate();
// 判斷輸贏
judgeLose();
 
break;
case 4:// 向右
System.out.println("右");
int zeroCountRight = 0;
 
// 逐行
for (int i = 0; i < 4; i++) {
int noSpaceCount = 0;
int n = 3;
// 中間數組
int[] tempArray = new int[4];
// 從右向左逐個
for (int j = 3; j >= 0; j--) {
 
// 把所有不是0 的複製到中間數組
if (array[i][j] != 0) {
tempArray[noSpaceCount] = array[i][j];
System.out.println("tempArray[" + noSpaceCount
+ "]=" + tempArray[noSpaceCount]);
noSpaceCount++;
}
}
// 開始返還
for (int m = 0; m < 4; m++) {
if (m == 0) {// 第一個直接複製,array計數變化
array[i][n] = tempArray[m];
n--;
 
} else if (m > 0 && tempArray[m] != 0
&& tempArray[m] != tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個不同的,每一個都賦給array,n--
array[i][n] = tempArray[m];
n--;
} else if (m > 0 && tempArray[m] != 0
&& tempArray[m] == tempArray[m - 1]) {// 從第二個開始,不等於0且與前一個相同的,把前一array*2,自己變成-1,n不增加,分數增加
array[i][n + 1] = array[i][n + 1] + array[i][n + 1];
tempArray[m] = -1;
score += array[i][n + 1];
}
}
for (int j = 0, f = 3; j < 4; j++, f--) {
if (tempArray[f] != array[i][j]) {
// System.out.println("tempArray["+j+"]="+tempArray[j]+",array["+i+"]["+j+"]="+array[i][j]);
zeroCountRight++;
 
}
}
// 把餘下的歸零
for (int k = 0; k <= n; k++) {
array[i][k] = 0;
}
}
if (zeroCountRight > 0) {
// 在空處隨機生成一個2或4
randomNew();
}
 
if (score > maxScore) {
maxScore = score;
}
// 刷新界面,畫新數字
this.postInvalidate();
// 判斷輸贏
judgeLose();
 
break;
}
break;
 
}
return true;
}
 
/**
 * 在空處隨機生成一個2或4的方法
 * 
 * @param canvas
 */
public void randomNew() {
Random random = new Random();
int r;
int c;
do {
r = random.nextInt(4);
c = random.nextInt(4);
} while (array[r][c] != 0);
 
int t = random.nextInt(2) * 2 + 2;
// 存儲數據
array[r][c] = t;
}
 
/**
 * 判斷輸贏的方法
 */
public void judgeLose() {// 滿格而且任意行/列都沒有連續相同的兩個滑塊
int checkCount = 0;
int sameCount = 0;
 
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if ((array[i])[j] == 0) {
checkCount++;
}
if (j > 0) {
if (array[i][j] == array[i][j - 1]) {
sameCount++;
}
}
if (i > 0) {
if (array[i][j] == array[i - 1][j]) {
sameCount++;
}
}
 
}
}
if (checkCount == 0 && sameCount == 0) {
OnClickListener bl = new ButListener(maxScore, ma);
 
new AlertDialog.Builder(ma).setTitle("遺憾").setMessage("到此爲止")
 
.setPositiveButton("重新開始", new DialogInterface.OnClickListener() {
 
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
 
score = 0;
 
TextView maxS = (TextView) ma.findViewById(R.id.maxScore);
maxS.setText("最高分:" + maxScore);
 
Game2048 game = (Game2048) ma.findViewById(R.id.game1);
 
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
array[i][j] = 0;
}
}
game.postInvalidate();
 
}
 
})
 
.show();
}
 
}
 
}


************************************************************



package com.example.android2048;
 
import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
 
 
 
public class ButListener implements OnClickListener{
private int maxScore=0;
private Activity ma;
public ButListener(int maxScore,Context context){
this.maxScore=maxScore;
this.ma= (Activity)context;
}
public void onClick(View v){
 Button but = (Button)v;
 if (but.getText().equals("重新開始")){
 
 Game2048 game = (Game2048)ma.findViewById(R.id.game1);
 this.maxScore=game.getMaxScore();
 game.setScore(0);//優先級高於maxS.setText("最高分:"+maxScore);的形式
 for(int i=0;i<4;i++){
 for(int j=0;j<4;j++){
 game.setArray(i, j, 0);
 game.postInvalidate();
 }
 }
 
 
 TextView maxS = (TextView)ma.findViewById(R.id.maxScore);
 maxS.setText("最高分:"+maxScore);
 
 
 
 
 }
 }
}

*********************************************************************


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">
 
    <com.example.android2048.Game2048
       android:id="@+id/game1"
       android:layout_width="wrap_content"
       android:layout_height="300dp"
       android:layout_alignParentBottom="true"
       android:layout_alignParentLeft="true"
       android:layout_alignParentRight="true"/>
 
    <TextView
       android:id="@+id/score"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignLeft="@+id/game1"
       android:layout_alignParentTop="true"
       android:layout_marginTop="39dp"
       android:text="當前分數:0"/>
 
    <TextView
       android:id="@+id/maxScore"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignBaseline="@+id/score"
       android:layout_alignBottom="@+id/score"
       android:layout_centerHorizontal="true"
       android:text="最高分:0"/>
 
    <Button
       android:id="@+id/butReStart"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignBaseline="@+id/maxScore"
       android:layout_alignBottom="@+id/maxScore"
       android:layout_alignRight="@+id/game1"
       android:text="重新開始"/>
 
</RelativeLayout>


 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章