[Android]自己動手做個拼圖遊戲
目標
在做這個遊戲之前,我們先定一些小目標列出來,一個一個的解決,這樣,一個小遊戲就不知不覺的完成啦。我們的目標如下:
1. 遊戲全屏,將圖片拉伸成螢幕大小,並將其切成若干塊。
2. 將拼圖塊隨機打亂,並保證其能有解。
3. 在螢幕上留出一個空白塊,當點空白塊旁邊的塊,將這塊移動到空白塊。
4. 判斷是否已經拼好。
實現目標
1.將圖片拉伸成螢幕大小,並將其切成若干塊。
想拉伸成螢幕大小,首先要知道螢幕的大小,Android獲得螢幕大小的程式碼如下:
DisplayMetrics metrics =new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(metrics);//sdk17+
int screenWidth = metrics.widthPixels;//螢幕寬
int screenHeight = metrics.heightPixels;//螢幕高
將圖片拉伸到螢幕大小
Bitmap back=Bitmap.createScaledBitmap(bitmap,
MainActivity.getScreenWidth(),
MainActivity.getScreenHeight(),
true);
將圖片切成若干塊
private final int COL=3;//列,預設3列
private final int ROW=3;//行,預設3行
int tileWidth=back.getWidth()/COL;//每一塊的寬
int tileHeight=back.getHeight()/ROW;//每一塊的高
Bitmap[] bitmapTiles =new Bitmap[COL*ROW];
int idx=0;
for(int i=0;i<ROW;i++)
{
for(int j=0;j<COL;j++)
{
bitmapTiles[idx++]=Bitmap.createBitmap(back,
j*tileWidth,
i*tileHeight,
tileWidth,tileHeight);
}
}
2. 將拼圖塊隨機打亂,並保證其能有解。
這個問題應該是這個小遊戲的核心了,有些人在做拼圖的時候就隨便亂擺,最後發現拼不回來,超級尷尬。要想打亂了還能拼回來,我們呢,就想到了模擬人打亂拼圖的方法,就是將空白塊與旁邊的非空白塊交換位置,與旁邊哪個非空白塊交換是隨機的,然後將這個過程重複若干次,重複的次數也是隨機的,這樣一來,保證了圖塊的隨機,又保證了能拼回來。在這裡我們用數字0到N-1(N為塊的數量)表示每一塊,並用二維陣列儲存他們。
private void createIntegerArray(int row,int col)
{
array=new int[row][col];
int idx=0;
for(int i=0;i<row;i++)
for(int j=0;j<col;j++)
array[i][j]=idx++;
}
下面是打亂塊的演算法,最後一塊是空白塊,讓它隨機與旁邊的某一塊進行交換,這個過程中要檢查陣列邊界,不要讓它越界。
//四個方向
private int[][] dir={
{0,1},//下
{1,0},//右
{0,-1},//上
{-1,0}//左
};
/**
* 移動塊的位置
* @param srcX 初始x位置
* @param srcY 初始y位置
* @param xOffset x偏移量
* @param yOffset y偏移量
* @return 新的位置,錯誤返回new Point(-1,-1);
*/
private Point move(int srcX,int srcY,int xOffset,int yOffset)
{
int x=srcX+xOffset;
int y=srcY+yOffset;
if(x<0||y<0||x>=col||y>=row)
return new Point(-1,-1);
int temp=array[y][x];
array[y][x]=array[srcY][srcX];
array[srcY][srcX]=temp;
return new Point(x,y);
}
/**
* 得到下一個可以移動的位置
* @param src 初始的點
* @return
*/
private Point getNextPoint(Point src)
{
Random rd=new Random();
int idx=rd.nextInt(4);//,因為有4個方向,所以產生0~3的隨機數
int xOffset=dir[idx][0];
int yOffset=dir[idx][1];
Point newPoint=move(src.getX(),src.getY(),xOffset,yOffset);
if(newPoint.getX()!=-1&&newPoint.getY()!=-1) {
return newPoint;//找到了新的點
}
return getNextPoint(src);//沒有找到,繼續
}
/**
* 生成拼圖資料
* @param row
* @param col
* @return
*/
public int[][] createRandomBoard(int row,int col)
{
if(row<2||col<2)
throw new IllegalArgumentException("行和列都不能小於2");
this.row=row;
this.col=col;
createIntegerArray(row,col);//初始化拼圖資料
int count=0;
Point tempPoint=new Point(col-1,row-1);//最後一塊是空白塊
Random rd=new Random();
int num=rd.nextInt(100)+20;//產生20~119的隨機數,表示重複的次數
while (count<num)
{
tempPoint=getNextPoint(tempPoint);//獲得下個點,並更新空白塊位置
count++;
}
return array;
}
3. 在螢幕上留出一個空白塊,當點空白塊旁邊的塊,將這塊移動到空白塊。
留出空白塊很簡單,由於上面我們將最後一塊作為空白塊。當我們繪圖時,略過它即可。程式碼實現如下:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.GRAY);
for(int i=0;i<ROW;i++) {
for (int j = 0; j < COL; j++) {
int idx=dataTiles[i][j];
if(idx==ROW*COL-1&&!isSuccess)
continue;
canvas.drawBitmap(bitmapTiles[idx],
j*tileWidth,
i*tileHeight,paint);
}
}
}
移動塊也很簡單,當點選螢幕時,計算其在拼圖資料中對應的索引。當計算到點選非空白塊就尋找它旁邊有沒有空白塊,有,則將拼圖資料中表示空白塊和非空白塊的資料交換,並重新整理View即可
/**
* 將螢幕上的點轉換成,對應拼圖塊的索引
* @param x
* @param y
* @return
*/
private Point xyToIndex(int x,int y)
{
int extraX=x%tileWidth>0?1:0;
int extraY=x%tileWidth>0?1:0;
int col=x/tileWidth+extraX;
int row=y/tileHeight+extraY;
return new Point(col-1,row-1);
}
/**
*點選螢幕時發生
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN) {
Point point = xyToIndex((int) event.getX()
, (int) event.getY());
for(int i=0;i<dir.length;i++)
{
int newX=point.getX()+dir[i][0];
int newY=point.getY()+dir[i][1];
if(newX>=0&&newX<COL&&newY>=0&&newY<ROW){
if(dataTiles[newY][newX]==COL*ROW-1)
{
int temp=dataTiles[point.getY()][point.getX()];
dataTiles[point.getY()][point.getX()]=dataTiles[newY][newX];
dataTiles[newY][newX]=temp;
invalidate();
}
}
}
}
return true;
}
4. 判斷是否已經拼好
我們初始化資料時,是從0開始,依次增加作為拼圖資料。當拼好的時候,拼圖資料也應該是一樣的,所以我們比較陣列中每一個數據與它的下一個資料,如果每一個數據都小於它的下一個資料,說明數組裡面的資料已經從小到大排列好。
/**
* 判斷是否拼圖成功
* @param arr
* @return
*/
public boolean isSuccess(int[][] arr)
{
int idx=0;
for(int i=0;i<arr.length;i++)
{
for(int j=0;j<arr[i].length&&idx<row*col-1;j++)
{
if(arr[idx/row][idx%col]>arr[(idx+1)/row][(idx+1)%col])
{
return false;
}
idx++;
}
}
return true;
}
拼圖遊戲技巧
拼圖技巧覺得也有必要說一下,不然有些人就會說:你的演算法有問題,這根本拼不好!我也是超級無奈啊!拼圖的技巧是,我們先把上面的第一行拼好,然後再把第二行拼好,這樣,一直下去~就能完全拼好了。
總結
這個小遊戲簡單,可以拿來練手,還可以拿來裝(liao)逼(mei),如果不會,何樂而不看呢。這個小遊戲也是將檢視和資料分開,程式碼容易移植。