helloPe的android項目實戰之連連看—實現篇(二)

來源:互聯網
上載者:User

  文接上回,之前介紹了項目的架構,進行了功能的分析,同時進行了BoardView類及時間控制類的開發及幾個幾口的介紹。這次我們將完整的實現遊戲棋盤的繪製與touch事件的處理,以及遊戲核心演算法中串連演算法、hint自動協助演算法與判斷是否無解演算法的實現。這些代碼的處理都在繼承自BoardView類的GameView類中。

首先在GameView類中添加實現本遊戲主要演算法的代碼,即串連演算法的代碼(用於判斷給定的兩個位置的表徵圖能夠相連通):

/** * 本遊戲的核心演算法,判斷兩個連接點是否能夠串連,這裡傳進來的就是我們點擊的兩個點轉化成index的值 * @param p1 * @param p2 */List<Point> p1Expand = new ArrayList<Point>();List<Point> p2Expand = new ArrayList<Point>();public boolean link(Point p1,Point p2){if(p1.equals(p2)){return false;}path.clear();if(map[p1.x][p1.y] == map[p2.x][p2.y]){if(linkDirect(p1,p2)){path.add(p1);path.add(p2);return true;}/** * 一個拐點的判斷 */Point px = new Point(p1.x,p2.y);         //假設第一種可能點if(map[p1.x][p2.y] == 0 && linkDirect(p1,px) && linkDirect(px,p2)){path.add(p1);path.add(px);path.add(p2);return true;}Point py = new Point(p2.x,p1.y);        //假設第二種可能點if(map[p2.x][p1.y] == 0 && linkDirect(p1,py) && linkDirect(py,p2)){//首先判斷map[p2.x][p1.y]中介點是否有表徵圖path.add(p1);path.add(py);path.add(p2);return true;}/** * 兩個折點(corner) */expandX(p1,p1Expand);expandX(p2,p2Expand);for(int i = 0; i < p1Expand.size(); i++)for(int j = 0; j < p2Expand.size(); j++){if(p1Expand.get(i).x == p2Expand.get(j).x){if(linkDirect(p1Expand.get(i),p2Expand.get(j))){path.add(p1);path.add(p1Expand.get(i));path.add(p2Expand.get(j));path.add(p2);return true;}}}expandY(p1,p1Expand);expandY(p2,p2Expand);for(Point exp1:p1Expand)for(Point exp2:p2Expand){if(exp1.y == exp2.y){if(linkDirect(exp1,exp2)){path.add(p1);path.add(exp1);path.add(exp2);path.add(p2);return true;}}}return false;  //最後三種方式都不能連通,還是要return false ,不然在兩個同樣的表徵圖下卻沒有傳回值!}return false;}/** * 判斷直線連結,無拐角,傳進來的點值是ScreenToIndex過的了,不過這裡傳進來的不一定就是我們點擊的點,也可能是我們的拐角點(輔助點) * @param p1 * @param p2 */public boolean linkDirect(Point p1,Point p2){//if(map[p1.x][p1.y] == map[p2.x][p2.y]){//縱向直線if(p1.x == p2.x){int y1 = Math.min(p1.y, p2.y);int y2 = Math.max(p1.y, p2.y);boolean flag = true;for(int y = y1 + 1; y < y2; y++){//這個迴圈裡容易漏掉兩個相鄰的情況,所以才加上上面的flag樣式if(map[p1.x][y] != 0){flag = false;break;}}if(flag){return true;}}//橫直線判斷if(p1.y == p2.y){int x1 = Math.min(p1.x, p2.x);int x2 = Math.max(p1.x, p2.x);boolean flag = true;for(int x = x1 + 1; x < x2; x++){if(map[x][p1.y] != 0){flag = false;break;}}if(flag){return true;}}//}return false;}/** * 向x方向擴充,傳進來的點是index過的 * @param p * @param list */public void expandX(Point p,List<Point> list){list.clear();for(int x = p.x + 1; x < xCount; x++){//注意此時可以等於xCount -1了if(map[x][p.y] != 0)break;list.add(new Point(x,p.y));}for(int x = p.x -1; x >= 0; x--){if(map[x][p.y] != 0)break;list.add(new Point(x,p.y));}}/** * 向Y方向擴充,傳進來的點是index過的,而list是作為“傳回值”需要儲存的值 * @param p * @param list */public void expandY(Point p,List<Point> list){list.clear();for(int y = p.y + 1; y < yCount; y ++){if(map[p.x][y] != 0)break;list.add(new Point(p.x,y));}for(int y = p.y -1 ; y >= 0; y--){if(map[p.x][y] != 0)break;list.add(new Point(p.x,y));}}

代碼中盡量添加註釋,此段代碼中實現了第一篇文章中進行的演算法分析,其中link(Point p1,Point p2)函數作為演算法真正的完整實現者,演算法的主邏輯有它實現,

linkDirect(Point p1,Point p2)函數作為一個工具函數,用於判斷給定的兩個位置(注意不是兩個表徵圖,因為給定的位置不一定含有表徵圖,當我們在判斷

”一折型“和“二折型”的情況的時候即使如此)。而expandX(Point p,List<Point> list)與 expandY(Point p,List<Point> list)兩個方法的同樣作為工具函數,

在判斷“二折型”情況時候將會使用,也就是前面所說的“橫向掃描”與“縱橫掃描”。而對於link(Point p1,Point p2)函數中,我們的邏輯還是將大問題化為小問題處理

最終還是分解到調用linkDirect(Point p1,Point p2)函數來進行“直線型”的處理。

以上即是程式的串連演算法的實現,除了程式演算法邏輯的理解之外,還需注意在判斷的時候,若能夠連通,我們已經將

private List<Point> path = new ArrayList<Point>();儲存連通路徑的path附上值,記得當link函數返回true時,path中即儲存了一條相通的路徑!完成了

串連演算法,下一步我們將依賴於串連演算法的實現,完成掃描是否當前地圖已經出現無解的情況,因為程式的地圖是隨機產生的,難免有時候會出現無解的情況;

下面我們將實現判斷是否處於無解狀態,實現函數:

/** *用於判斷是否當前已經無解  */public boolean die(){for(int y= 1; y < yCount; y++)              //表示從此行中的一個元素開始掃描(起點)for(int x = 1; x < xCount; x++){        //表示此行中指定列,組成掃描起點if(map[x][y] != 0){                 for(int j = y; j < yCount; j++){//表示正在被掃描的行if(j == y){//迴圈中的第一次掃描,為什麼特殊?因為此時不一定從一行中的第一個元素開始掃描for(int i = x + 1; i < xCount - 1; i++){if(map[x][y] == map[i][j] && link(new Point(x,y),new Point(i,j))){return false;}}}else{for(int i = 1; i < xCount -1; i++){if(map[x][y] == map[i][j] && link(new Point(x,y),new Point(i,j)))return false;}}}}}return true;}

代碼中也有相應注釋,每一次判斷相當於一次遍曆棋盤,同時注意,如果die()函數返回為false,這則證明link()函數返回了true!前面已經提醒過:當link返回true時,我們用於儲存連通路徑的path對象中已經儲存了一條連通路徑的點的集合,只不過在die()函數中運行得到的是按遍曆順序而來的,並不是我們所指定的兩個始點與終點兩個表徵圖;所以在這兒,可以借die()的判斷,完成我們演算法實現的第三個功能,即hint的自動協助!

/** * 當點擊help按鈕時候調用,會協助玩家消除一對表徵圖 */public void autoHelp(){if(help == 0){//soundPlay.play(ID_SOUND_ERROR, 0);return ;}else{//soundPlay.play(ID_SOUND_TIP, 0);help--;toolsChangedListener.onTipChanged(help);drawLine(path.toArray(new Point[] {}));refreshHandler.sendRefresh(500);}}

當然此處需要介紹一下最後一行代碼的來曆:

class RefreshHandler extends Handler{@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if(msg.what == REFRESH_VIEW){GameView.this.invalidate();if(win()){setMode(WIN);isStop = true;isContinue = false;}else if(die()){    //調用一次die方法!此時如果die返回為false,即還能夠連通change();       //由於die中使用link方法檢測,所以此時path中的值又添加了進去,}                   //這對於我們使用autoHelp方法提供便利!!!}}/** *  * @param delayTime */public void sendRefresh(int delayTime){Message msg = new Message();this.removeMessages(0);msg.what = REFRESH_VIEW;this.sendMessageDelayed(msg, delayTime);}}

當然對於是否已經為贏了的判斷win()函數比較簡單,就是掃描棋盤,如果所有位置map值都為了0,即贏了,若不是,還未完成;這裡就不貼代碼了。

GameView類中還有一個職能就是初始化一張棋盤:

/** * 初始化地圖 */public void initMap(){int x = 1;int y = 0;for(int i = 1; i < xCount -1; i++)for(int j =1; j < yCount -1; j++){map[i][j] = x;if(y == 1){x ++;y = 0;if(x == iconCounts){x = 1;}}else{y = 1;}}change();GameView.this.invalidate();}

我們初始化棋盤時,利用前面講解的初始演算法技術,遍曆棋盤,先將棋盤填滿,但是填滿首先還有一個規則就是每一種表徵圖的填入必須同時填入兩張,是為每種表徵圖都為偶數個而設定!介紹一下最後調用的change()函數,也是出自於第一篇的棋盤初始演算法,用於隨機將棋盤中的表徵圖打亂:

 /**     * 隨機將現有的布局打亂,重新布局,map中現有表徵圖數量不變,相當於一次refresh     */public void change(){Random random = new Random();int tmp,xtmp,ytmp;for(int x = 1;x < xCount -1; x++){for(int y = 1; y < yCount -1; y++){xtmp = 1 + random.nextInt(xCount -2);ytmp = 1 + random.nextInt(yCount - 2);tmp = map[x][y];map[x][y] = map[xtmp][ytmp];map[xtmp][ytmp] = tmp;}}if(die()){              //如出現無解情況,即需要再次隨機重新打亂change();}}

GameView類還是一個View,在此類中我們還要重寫View的onTouchEvent方法:

/** * 對於選擇的處理,如果是第一次按下,則將其加入到selected當中, * 若是第二次(selected.size()==1),則先判斷能不能連通 */@Overridepublic boolean onTouchEvent(MotionEvent event) {int sx = (int)event.getX();int sy = (int)event.getY();Point p = screenToIndex(sx, sy);if(map[p.x][p.y] != 0){if(selected.size() == 1){if(link(selected.get(0),p)){   //能夠連通,path中的資料是在link判斷時如果返回真,方法內部就已經將資料添加進去selected.add(p);drawLine(path.toArray(new Point[]{}));refreshHandler.sendRefresh(500);}else{         //不能夠連通selected.clear();selected.add(p);GameView.this.invalidate();   //在這兒說一下refreshHanler.sendRefresh(int) 跟單純調用GameView.this.invalidate()區別                              //前者除了後者只擁有的重新整理顯示之外,還加了是否已經無解跟是否已經完成任務的判斷的操作。}}else{//此時的selected中的size只能等於0selected.add(p);GameView.this.invalidate();}}return super.onTouchEvent(event);}

方法中用到的selected是BoardView中的protected List<Point> selected = new ArrayList<Point>();代碼中對於功能及實現有相應的注釋。

到此我們可以提供介面startGame以供在程式的activity中調用:

public void startPlay(){help = 3;refresh = 3;isContinue = true;isStop = false;toolsChangedListener.onRefreshChanged(refresh);toolsChangedListener.onTipChanged(help);leftTime = totalTime;initMap();refreshTime = new RefreshTime();Thread t = new Thread(refreshTime);    //注意正確啟動一個實現Runnable介面的線程類t.start();GameView.this.invalidate();}

注意GameView中並沒有實現相關的自訂的介面,而是我們將會在程式的activity中實現項目中涉及的三個介面,但是,我們可以在GameView中進行註冊:

public void setOnTimerListener(OnTimerListener onTimerListener){this.timerListener = onTimerListener;}public void setOnToolsChangedListener(OnToolsChangeListener toolsChangeListener){this.toolsChangedListener = toolsChangeListener;}public void setOnStateChangeListener(OnStateListener stateListener){this.stateListener = stateListener;}

然後在程式的activity中調用GameView的相關函數進行初始化註冊。這樣,根據多態性的原理,在GameView當中調用的相關介面中的函數,也就是activity中實現的介面中的函數。這也是android程式中interface實現與註冊的一種方式。

以上已經基本描述了GameView的功能與最主要的實現。總結一下,實現了map的初始化,重寫了touch時間的處理函數,完成了程式的串連演算法,hint自動協助演算法,die的無解判斷演算法,還有用於更新顯示的繼承自Handler的內部類的實現。整個項目也已經基本成型了。

重申一下:之所以寫本系列的文章,為了記錄android小項目的經曆,增加實戰的能力,做個總結。並不是為了做出多麼新穎的項目,當然也是向不少的網友學習了的!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.