具體代碼的實現托管到了GitHub:https://github.com/ydd997/Android_pintu
下面介紹重要的幾個模塊。
時間改變的實現
在 MainActivity.java 中的 onCreate 函數中添加如下內容:
//一進來每隔1s就發一條空消息出去,接收到這個空消息並讓TextView發生改變,形成計數器的效果
//延遲1s發送一條空消息:發送消息的編號是1,延遲時間為1000ms=1s
handler.sendEmptyMessageDelayed(1,1000);
之后在 onCreate 函數外添加如下內容:
//定義計數時間的變量
int time=0;
//消息通信機制,重寫handleMessag方法用來接收消息,形成時間累計的效果
Handler handler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if(msg.what==1){
time++;
timeTv.setText("時間:"+time+"秒");
handler.sendEmptyMessageDelayed(1,1000);
}
}
};
時間變化的情況有兩種,一是當拼圖游戲完成的時候時間要停止,二是重新開始的時候要重新計時,現在先實現點擊重新開始按鈕時重新計時:
public void restart(View view) {
//中斷之前的消息
handler.removeMessages(1);
//將時間重新歸零並且重新開始計時
time=0;
timeTv.setText("時間:"+time+"秒");
handler.sendEmptyMessageDelayed(1,1000);
}
拼圖打亂顯示
思路:通過數組的隨機排位來使圖片隨機排列
首先聲明一些變量:
//存放碎片的數組,便於統一管理
private int[]image={R.mipmap.img_00x00,R.mipmap.img_00x01,R.mipmap.img_00x02,R.mipmap.img_01x00,R.mipmap.img_01x01,R.mipmap.img_01x02,R.mipmap.img_02x00,R.mipmap.img_02x01,R.mipmap.img_02x02};
//聲明一個圖片數組的下標數組,隨機排列這個數組
private int[]imageIndex=new int[image.length];
接下來寫一個打亂的方法,使得 onCreate 一進入就顯示打亂的數組。
首先打亂角標,接着把亂序的角標設置到ImageButton中,保證一進入就是打亂的狀態,具體代碼如下:
//隨機打亂數組中的元素,以不規則的形式進行圖片顯示
private void disruptRandom() {
for (int i = 0; i < imageIndex.length; i++) {
imageIndex[i]=i;
}
//規定20次,隨機選擇兩個角標對應的值進行交換
int rand1,rand2;
for (int j = 0; j < 20; j++) {
//隨機生成第一個角標,生成0—8之間的隨機數
rand1=(int)(Math.random()*(imageIndex.length-1)); //Math.random()生成的是0—1之間的隨機數,再乘以最大值減去最小值(即8-0),最后整體加上最小值0
//第二次隨機生成的角標不能和第一次相同,如果相同就不方便交換
do {
rand2=(int)(Math.random()*(imageIndex.length-1));
if(rand1!=rand2)
break;
}while (true);
//交換數組兩個角標上對應的值
swap(rand1,rand2);
}
//隨機排列到指定的控件上
ib00.setImageResource(image[imageIndex[0]]);
ib01.setImageResource(image[imageIndex[1]]);
ib02.setImageResource(image[imageIndex[2]]);
ib10.setImageResource(image[imageIndex[3]]);
ib11.setImageResource(image[imageIndex[4]]);
ib12.setImageResource(image[imageIndex[5]]);
ib20.setImageResource(image[imageIndex[6]]);
ib21.setImageResource(image[imageIndex[7]]);
ib22.setImageResource(image[imageIndex[8]]);
}
//交換數組指定角標的數據
private void swap(int rand1, int rand2) {
int temp=imageIndex[rand1];
imageIndex[rand2]=imageIndex[rand1];
imageIndex[rand1]=temp;
}
碎片位置切換
碎片移動的條件:空白區域只能和向同行或者相同列上並且是相鄰的圖片進行移動,否則不能完成。
需要判斷空白位置和其他圖的位置是否在同一行或同一列中。
移動的過程寫在 onClick 中:
public void onClick(View view) {
int id = view.getId();
//九個按鈕執行點擊事件的邏輯是相同的:如果有空格在周圍則可以改變圖片的位置,否則點擊事件不響應
switch (id){
case R.id.pt_ib_00x00:
move(R.id.pt_ib_00x00,0);
break;
case R.id.pt_ib_00x01:
move(R.id.pt_ib_00x01,1);
break;
case R.id.pt_ib_00x02:
move(R.id.pt_ib_00x02,2);
break;
case R.id.pt_ib_01x00:
move(R.id.pt_ib_01x00,3);
break;
case R.id.pt_ib_01x01:
move(R.id.pt_ib_01x01,4);
break;
case R.id.pt_ib_01x02:
move(R.id.pt_ib_01x02,5);
break;
case R.id.pt_ib_02x00:
move(R.id.pt_ib_02x00,6);
break;
case R.id.pt_ib_02x01:
move(R.id.pt_ib_02x01,7);
break;
case R.id.pt_ib_02x02:
move(R.id.pt_ib_02x02,8);
break;
}
}
其中 move 函數具體實現過程:
/*表示移動指定位置的按鈕的函數:將圖片和空白區域進行交換*/
private void move(int imageButtonId, int site) {
//判斷選中的圖片在第幾行,取整來判斷
int sitex=site / imageX;
//判斷選中的圖片在第幾列,趨於來判斷
int sitey=site % imageY;
//獲取空白區域的坐標
int blackx=blackSwap / imageX;
int blacky=blackSwap % imageY;
//可以移動的條件
//1.在同一行,列數相減絕對值為1,可以移動;2.在同一列,行數相減絕對值為1,可以移動。
int x=Math.abs(sitex-blackx);
int y=Math.abs(sitey-blacky);
if((x==0&&y==1)||(x==1&&y==0)){
//通過id查找到這個可以移動的按鈕
ImageButton clickButton=findViewById(imageButtonId);
//該可移動按鈕不在顯示圖片
clickButton.setVisibility(View.INVISIBLE);
//查找空白區域的按鈕
ImageButton blackButton=findViewById(blackImgid);
//將空白按鈕設置為顯示圖片
blackButton.setImageResource(image[imageIndex[site]]);
//移動之前是不可見的,移動之后將控件設置為可見
blackButton.setVisibility(View.VISIBLE);
//上面的交換並沒有存在數組之中,要調用swap函數,將改變角標的過程記錄在存儲圖片位置的數組當中
swap(site,blackSwap);
//新的空白區域位置更新
blackSwap=site;
blackImgid=imageButtonId;
}
}
目前為止效果展示:
拼圖成功的條件
每次拼圖結束之后,要判斷一下當前拼圖順序是否滿足正確順序。
這里是判斷存放圖片角標的數組的順序。
/*判斷拼圖是否成功*/
private void judgeGameOver() {
boolean loop=true; //定義標志位
//對存放圖片角標的數組imageIndex進行判斷
for (int i = 0; i < imageIndex.length; i++) {
if (imageIndex[i]!=i) {
loop=false;
break;
}
}
if (loop) {
//拼圖成功
//停止計時
handler.removeMessages(1); //移除消息
//禁止玩家繼續移動按鈕
ib00.setClickable(false);
ib01.setClickable(false);
ib02.setClickable(false);
ib10.setClickable(false);
ib11.setClickable(false);
ib12.setClickable(false);
ib20.setClickable(false);
ib21.setClickable(false);
ib22.setClickable(false);
//顯示之前隱藏的拼圖
ib22.setImageResource(image[8]);
ib22.setVisibility(View.VISIBLE);
//彈出對話框
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setMessage("拼圖成功啦!您用的時間是"+time+"秒!")
.setPositiveButton("確認",null);
AlertDialog dialog = builder.create();
dialog.show();
}
}
游戲重新開始
現在需要將拼圖重新打亂,重新打亂的函數 disruptRandom() 在前面已經寫好,直接引用即可。
但是之前游戲成功已經禁止拼圖繼續移動,所以將打亂之前要恢復按鈕可以移動的功能,同時還要還原被點擊圖片初始化的樣子,具體代碼如下:
/*重新開始按鈕的點擊事件*/
public void restart(View view) {
//將狀態還原
restore();
//將拼圖重新打亂
disruptRandom();
//中斷之前的消息
handler.removeMessages(1);
//將時間重新歸零並且重新開始計時
time=0;
timeTv.setText("時間:"+time+"秒");
handler.sendEmptyMessageDelayed(1,1000);
}
private void restore() {
//拼圖游戲重新開始,允許玩家重新觸碰按鈕
ib00.setClickable(true);
ib01.setClickable(true);
ib02.setClickable(true);
ib10.setClickable(true);
ib11.setClickable(true);
ib12.setClickable(true);
ib20.setClickable(true);
ib21.setClickable(true);
ib22.setClickable(true);
//還原被點擊的圖片按鈕變成初始化的模樣
//最后一次選中的空白區域顯示出來
ImageButton clickButton=findViewById(blackImgid);
clickButton.setVisibility(View.VISIBLE);
//定義一個新的圖片按鈕,設置為第九個,讓其隱藏(默認隱藏第九張圖片)
ImageButton blackBtn=findViewById(R.id.pt_ib_02x02);
blackBtn.setVisibility(View.INVISIBLE);
//初始化空白區域的按鈕id
blackImgid=R.id.pt_ib_02x02;
blackSwap=imageCount-1;
}
}
最終效果:
思考
整個過程不是很難理解,只有一個頁面,布局也比較簡單,采用了最基本的線性布局。感覺比較適合入門。在打亂九個碎片的順序時候采用了兩個數組,第一先將九個碎片按照正確的順序存儲在一個數組中,第二用一個數組存儲碎片存放的位置,通過打亂放置位置的數組來打亂圖片。接着是切換點擊,能夠將碎片和空白位置進行交換,之后是要判斷何時游戲成功,判斷完成點擊重新開始又可以重新打亂順序進行游戲。
