開始!
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>