1. 程式人生 > >安卓2048原始碼分析

安卓2048原始碼分析

2048遊戲最近很火,想看下原始碼,卻不會JavaScript。網上搜了搜安卓版的原始碼,嘗試下來學習。

 uberspot

在https://github.com/uberspot/2048-android 上面發現了一個安卓版的2048程式碼,於是下載準備閱讀。卻發現原始檔中只有一個Java類,MainActivity.java。開啟大致看了一下:

// If there is a previous instance restore it in the webview
if (savedInstanceState != null) {
    mWebView.restoreState(savedInstanceState);
} 
else { mWebView.loadUrl("file:///android_asset/2048/index.html"); }

原來是用一個webview對原本的JavaScript進行了封裝,使用安卓內部webkit瀏覽器進行了載入。相當於用手機瀏覽器玩網頁版的遊戲,只能再搜尋了。

極客學院

另外找到一個極客學院版本的原始碼,在網站上面還有視訊教程。本文分析的主要內容就是極客學院版本的原始碼了,作者是ime。

原始碼連結:https://github.com/plter/Android2048GameLesson

分析的目標為code\ide\ADT\Game2048Publish目錄中的原始碼版本

 1 介面

介面比較簡單了,開啟activity_main.xml看看,幾個TextView,一個按鈕,還有三個自定義的控制元件GameView,AnimLayer,Card。遊戲的截圖如下:

image

標準控制元件就不介紹了,介紹一下系統三個自定義的控制元件。

 1.1 Card(後文混用Card 卡片 方塊三個詞語)

類Card繼承了FrameLayout,目的是作為遊戲中的卡片。卡片數字和樣式的實現:

public void setNum(int num) {
        this.num = num;

        if (num<=0) {
            label.setText(
""); }else{ label.setText(num+""); } switch (num) { case 0: label.setBackgroundColor(0x00000000);//透明色 break; case 2: label.setBackgroundColor(0xffeee4da); break; case 4: label.setBackgroundColor(0xffede0c8); break; case 8: label.setBackgroundColor(0xfff2b179); break; case 16: label.setBackgroundColor(0xfff59563); break; …… default: label.setBackgroundColor(0xff3c3a32); break; } }

num<=0表明是空白方格。當前位置上如果沒有card,則使用num<=0的card進行替代。card 0沒有label,同時底色為透明。除了card 0之外,card 2之後的卡片都有對應的顏色和數字。

 1.2 AnimLayer

類AnimLayer繼承了FramLayout,用於動畫展示。在極客學院安卓2048最主要由兩個動畫:卡片移動和卡片出現。

a) 對於卡片出現動畫:

//目標卡片
public void createScaleTo1(Card target){
    //縮放
     ScaleAnimation sa = new ScaleAnimation(0.1f, 1, 0.1f, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    sa.setDuration(100);
    target.setAnimation(null);
    target.getLabel().startAnimation(sa);
   }

b) 對於卡片移動動畫:

使用ArrayList<Card> cards用於管理臨時卡片的建立和回收(避免每次建立臨時卡片時建立新的物件)
建立一個臨時卡片,從卡片from移動到卡片to,當完成動畫之後將臨時卡片設為不可見,並使用cards回收該卡片。
建立卡片:
private Card getCard(int num){
    Card c;
    if (cards.size()>0) {
        c = cards.remove(0);
    }else{
        c = new Card(getContext());
        addView(c);
    }
    c.setVisibility(View.VISIBLE);
    c.setNum(num);
    return c;
  }

建立卡片時,如果cards不為空,則從cards隊首取出一張臨時卡片。(這裡認為使用LinkedList<Card>更加適合臨時卡片管理佇列)

回收卡片:

private void recycleCard(Card c){
    c.setVisibility(View.INVISIBLE);
    c.setAnimation(null);
    cards.add(c);
  }

回收卡片將當前卡片設為不可見,並加入到cards中。

卡片移動:

public void createMoveAnim(final Card from,final Card to,int fromX,int toX,int fromY,int toY){
    //臨時卡片
    final Card c = getCard(from.getNum());

    //設定佈局
    LayoutParams lp = new LayoutParams(Config.CARD_WIDTH, Config.CARD_WIDTH);
    lp.leftMargin = fromX*Config.CARD_WIDTH;
    lp.topMargin = fromY*Config.CARD_WIDTH;
    c.setLayoutParams(lp);

    if (to.getNum()<=0) {
        to.getLabel().setVisibility(View.INVISIBLE);
    }
    //從from卡片位置移動到to卡片
    TranslateAnimation ta = new TranslateAnimation(0, Config.CARD_WIDTH*(toX-fromX), 0, Config.CARD_WIDTH*(toY-fromY));
    ta.setDuration(25);
    ta.setAnimationListener(new Animation.AnimationListener() {

        @Override
        public void onAnimationStart(Animation animation) {}

        @Override
        public void onAnimationRepeat(Animation animation) {}

        //動畫結束,將臨時卡片回收
        @Override
        public void onAnimationEnd(Animation animation) {
            to.getLabel().setVisibility(View.VISIBLE);
            recycleCard(c);
        }
    });
    c.startAnimation(ta);

  }

1.3 GameView

GameView繼承了GridLayout,包含了介面和遊戲邏輯兩個部分。這裡介紹介面。

介面中比較重要的內容就是手勢識別,用於操控格子的移動:

private void initGameView(){
    setColumnCount(Config.LINES);
    setBackgroundColor(0xffbbada0);
    setOnTouchListener(new View.OnTouchListener() {

        private float startX,startY,offsetX,offsetY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN://按下座標
                    startX = event.getX();
                    startY = event.getY();
                    break;
                case MotionEvent.ACTION_UP:
                    offsetX = event.getX()-startX;
                    offsetY = event.getY()-startY;
                    if (Math.abs(offsetX)>Math.abs(offsetY)) {
                        if (offsetX<-5) {
                            swipeLeft();
                        }else if (offsetX>5) {
                            swipeRight();
                        }
                    }else{
                        if (offsetY<-5) {
                            swipeUp();
                        }else if (offsetY>5) {
                            swipeDown();
                        }
                    }
                    break;
            }
            return true;//listener已經處理了事件
        }
    });
     }

使用了View.OnTouchListener來偵聽觸控事件:計算按下和擡起來時offsetX和offsetY,預測手勢的移動。

2 遊戲邏輯

上一節介紹了基本的介面展現,本節介紹業務邏輯,即遊戲實現原理。

2.1 遊戲初始化

呼叫函式initGameView()完成遊戲初始化:

private void initGameView(){
    setColumnCount(Config.LINES);//設定行數量
    setBackgroundColor(0xffbbada0);


    setOnTouchListener(new View.OnTouchListener() {
        }
    });
}

設定控制元件的方格數量,隨後設定了控制元件北京,最後註冊了剛才分析過的觸控事件監聽器。此時遊戲已經準備好了,正式開始。

 2.2 開始遊戲

函式startGame();正式開始遊戲,首先向方格內隨機寫入兩個方塊:

public void startGame(){

    MainActivity aty = MainActivity.getMainActivity();
    aty.clearScore();
    aty.showBestScore(aty.getBestScore());

    for (int y = 0; y < Config.LINES; y++) {
        for (int x = 0; x < Config.LINES; x++) {
            cardsMap[x][y].setNum(0);
        }
    }

    addRandomNum();
    addRandomNum();
}

這個函式addRandomNum()向遊戲面板內隨機加入兩個方塊,開始遊戲:

private void addRandomNum(){
    //private List<Point> emptyPoints = new ArrayList<Point>(); 
    emptyPoints.clear();

    //將所有空格子蒐集起來
    for (int y = 0; y < Config.LINES; y++) {
        for (int x = 0; x < Config.LINES; x++) {
            if (cardsMap[x][y].getNum()<=0) {
                emptyPoints.add(new Point(x, y));
            }
        }
    }

    if (emptyPoints.size()>0) {
        //隨機位置生成一個card
        Point p = emptyPoints.remove((int)(Math.random()*emptyPoints.size()));
        int num = Math.random()>0.1?2:4;
        cardsMap[p.x][p.y].setNum(num);
        MainActivity.getMainActivity().getAnimLayer().createScaleTo1(cardsMap[p.x][p.y]);
    }
}

函式addRandomNum()向面板中空的格子中隨機生成一個卡片。首先蒐集面板中所有空的位置,蒐集到一個List中,最後生成隨機數,隨機生成一個數字,並完成生成動畫。

 2.3 移動

2048遊戲通過遊戲中所有的方格朝某個方向移動,合併相同數字的方塊。有四個函式負責移動,分別是上下左右,這裡只分析一個方向。

private void swipeLeft(){

    boolean merge = false;//是否合併卡片, 1空卡片和已有卡片合併 2兩個數字相同的卡片合併

    for (int y = 0; y < Config.LINES; y++) {//對所有列
        for (int x = 0; x < Config.LINES; x++) {
            //檢查當前點的右側是否有非空卡片(非空:num>=2)
            for (int x1 = x+1; x1 < Config.LINES; x1++) {
                if (cardsMap[x1][y].getNum()>0) {//如果右邊有非空卡片

                    if (cardsMap[x][y].getNum()<=0) {//當前座標上沒有格子(空卡片和已有卡片合併)

                        MainActivity.getMainActivity().getAnimLayer().createMoveAnim(cardsMap[x1][y],cardsMap[x][y], x1, x, y, y);

                        cardsMap[x][y].setNum(cardsMap[x1][y].getNum());
                        cardsMap[x1][y].setNum(0);

                        x--;//和空卡片合併,還需要從當前位置計算(否則:|0|2|2|2|左移之後變為|2|2|2|0|)
                        merge = true;

                    }else if (cardsMap[x][y].equals(cardsMap[x1][y])) {
                        MainActivity.getMainActivity().getAnimLayer().createMoveAnim(cardsMap[x1][y], cardsMap[x][y],x1, x, y, y);
                        cardsMap[x][y].setNum(cardsMap[x][y].getNum()*2);
                        cardsMap[x1][y].setNum(0);

                        MainActivity.getMainActivity().addScore(cardsMap[x][y].getNum());
                        merge = true;
                    }

                    break;
                }
            }
        }
    }

    //只要有任意一行發生過卡片移動,則需要產生新的卡片
    if (merge) {

        addRandomNum();
        checkComplete();//判斷當前遊戲是否失敗
    }
}

左移,針對面板中所有列,將每行的方塊向左移動。在兩種情況發生卡片合併:

1 當前位置為空卡片,右側為非空卡片,合併後當前位置卡片Num為右側卡片,右側卡片清零。

2 當前位置為非空卡片,右側卡片數值和它相等,合併後當前位置卡片數量翻倍,右側卡片清零。

從遊戲角度來講:1 對應卡片單純的移動,2 對應兩張相同卡片的合併。因此,只要發生卡片實質上的移動,就應該隨機再生產一個卡片,呼叫addRandomNum()。

2.4 遊戲結束的判斷

每次發生卡片移動,都要檢查遊戲還能否繼續,是否已經結束。函式checkComplete()完成遊戲失敗(感覺叫做checkFailure()更好)的檢查:

private void checkComplete(){

    boolean complete = true;

ALL:
    for (int y = 0; y < Config.LINES; y++) {
        for (int x = 0; x < Config.LINES; x++) {
            //滿足任意兩個條件,遊戲就可以繼續:1 有空的格子,2 有可以合併的卡片
            if (cardsMap[x][y].getNum()==0||//1 有多餘空間
                    (x>0&&cardsMap[x][y].equals(cardsMap[x-1][y]))||//2 和左面相等
                    (x<Config.LINES-1&&cardsMap[x][y].equals(cardsMap[x+1][y]))|//2 和右面相等
                    (y>0&&cardsMap[x][y].equals(cardsMap[x][y-1]))||//2 和上面相等
                    (y<Config.LINES-1&&cardsMap[x][y].equals(cardsMap[x][y+1]))) {//2 和下面相等

                complete = false;
                break ALL;
        }
    }
    if (complete) {
        new AlertDialog.Builder(getContext()).setTitle("你好").setMessage("遊戲結束").setPositiveButton("重新開始", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                startGame();
            }
        }).show();
    }

}

遊戲可以繼續的兩個條件:有空的格子,或者還有能夠合併的卡片。

相關推薦

2048原始碼分析

2048遊戲最近很火,想看下原始碼,卻不會JavaScript。網上搜了搜安卓版的原始碼,嘗試下來學習。  uberspot 在https://github.com/uberspot/2048-android 上面發現了一個安卓版的2048程式碼,於是下載準備閱讀。卻發現原始檔中只有一個Java類,Mai

照相機原始碼分析1——Switcher類,ShutterButton類,RotateImageView類

   最近做的專案與安卓照相機有關,所以在網上下了安卓照相機的原始碼,個人對安卓開發也只是個初學者,照相機原始碼對本人而言還是很複雜(大概有70-80個類)。計劃以後每天研究幾個類,主要學習裡面程式設計的思想與經驗。今天首先對3個與介面有關的view類進行學習分析。 主要的

一文了解APP逆向分析與保護機制

dex 也不會 時也 也有 包含 啟動 RM 操作 混亂 “知物由學”是網易雲易盾打造的一個品牌欄目,詞語出自漢·王充《論衡·實知》。人,能力有高下之分,學習才知道事物的道理,而後才有智慧,不去求問就不會知道。“知物由學”希望通過一篇篇技術幹貨、趨勢解讀、人物思考和沈澱給你

app原始碼的獲取

反編譯有一種方式是 把dex檔案從apk解壓出來,得到classes.dex 然後用dex2jar把dex檔案轉換成jar檔案: dex2jar.bat classes.dex 再把生成的classes.jar檔案放到JD-GUI中即可 仍然報錯 dex2jar

Tencent2016A靜態逆向分析

安卓靜態逆向分析——Tencent2016A引入逆向步驟 引入 本文是我在學習安卓逆向時的一些步驟、心得,逆向的是2016年騰訊遊戲安全技術競賽提供的檔案,因為檔案比較簡單僅僅從靜態分析便能找到對應的註冊碼。 所用到的工具有: IDA、CodeBlocks。 逆

檢視APK原始碼破解

連線中有下載工具的連線; 第一步: 首先用解壓軟體(如好壓)等,把apk包解壓出來。其中解壓後的xml檔案開啟時亂碼,所以我們需要這樣做: 開啟cmd.exe進入到解壓後的資料夾中。輸入: java -jar AXMLPrinter2.jar showtimes_li

ESP8266 IOT物聯網SDK原始碼+app原始碼智慧家居WIFI開發板原理圖+視訊教程

筆者第一次接觸物聯網WIFI開發是安信可的小黃板測試板,當時安信可提供的資料很多,也很亂,沒有從零的系統講解。因為沒有得到專門的技術指導,後來又接觸了樂鑫,機智雲,開發快等等。面對這麼多開發廠商,對於初學者來說,不免有點茫然。筆者的學習之路也是不免坎坷,開發一款物聯網產品離

數千個Android專案原始碼遊戲原始碼大全經典專案附帶原始碼(文件版)

今天給大家分享下本人嘔心瀝血整理的上千個安卓原始碼,包括遊戲,安全 ,工具,商城等,內容非常全。這篇博文有兩個版本,上一個版本是圖片的,這一個是文字版的,方便大家Ctrl+F尋找自己想要的原始碼。 適合人群 想要做android開發的上班族:眾多經典案例,讓你瞭解快速

系統原始碼編譯系列(六)——單獨編譯內建瀏覽器WebView教程

本文主要對從安卓系統原始碼中抽取出WebView相關原始碼進行單獨編譯的流程進行說明。 編譯流程說明 由於WebView包含兩個部分,一部分是上層的Java程式碼,包括若干Java類,用於對外提供介面;另一部分是下層的C++程式碼,包括兩個so庫(libwebcore.

框架,分析解決專案中出現的anr

07-16 15:31:47.551 E/ActivityManager( 1775): Reason: Input dispatching timed out (Waiting because the focused window's input ch

2018年最牛的5個移動應用分析平臺

價格: 免費/付費支援的平臺: iOS 和 AndroidFirebase是谷歌針對移動應用開發者提供的分析平臺。您不僅可以訪問您的移動應用分析,還可以獲得在其平臺上用於開發應用的工具。但Firebase的核心是為您提供一個用於檢視使用者行為和各個網盟推廣效果的分析平臺。您還可以將原始資料匯出到BigQuer

系統原始碼編譯系列(三)——常用命令

在下載編譯完成安卓原始碼之後,我們在閱讀、除錯、修改安卓原始碼時,可能還需要對原始碼進行一系列操作,如切換分支、重置等,下面我們就來看看如何對原始碼進行一些常用操作。 模組單獨編譯 1.檢視當前可編譯的所有模組名稱 make modules 2.清除指定模組的編譯

系統原始碼編譯系列(一)——下載系統原始碼教程

最近需要編譯安卓系統,諮詢了一個編譯過安卓系統的朋友,說是下載原始碼就得下載兩天,於是做好了長期抗戰的準備,開始了下載安卓原始碼的旅程。在剛開始下載時,可以參照的內容只有官方教程,於是跟著官方教程一步一步走,遇到問題就百度谷歌,結果發現自己因為經驗不足走了很多彎路,寫下這篇

系統原始碼編譯系列(七)——單獨編譯WebView相容性問題解決

上一篇文章中,說明了單獨編譯WebView的流程,但是我們最後編譯出來的版本只能在對應系統版本的模擬器或者真機才能執行,下面我們就需要解決各個版本的相容問題。仔細分析不同版本執行時報的錯誤。 相容性問題解決 10-20 14:56:29.132: E/AndroidRunt

框架,分析專案中surfaceFlinger出現的bug ---queueBuffer: BufferQueue has been abandoned

播放視訊切換頁面後返回發現surfaceview黑屏了,錯誤日誌如下 E/BufferQueueProducer: queueBuffer: BufferQueue has been abandoned 看下日誌來源 //BufferQueueProduce

基礎工具分析與實踐

接下來一段時間,我決定把安卓的常用基礎工具做一個總結。以便給讀者一些啟示,算是拋磚引玉,或者是對自己研究心得的記錄吧。(格式寫的亂,讀者自行腦補分段吧。。。)   1. 一個問題:安卓native層開發為什麼可以用較少行程式碼實現一些複雜的業務邏輯操作? 對於這個問題,有些人可能覺得並不是,像And

強弱指標分析與測試

  好久沒有更新部落格了,前段時間實在是太懶。過年後從老家到工作地方只更新了一篇與技術無關的文章。接下來時間,期望自己多多寫部落格,技術部落格中也適當增加些逸聞趣事(例如這幾天隔壁金三胖子手術後生死成謎),生活類部落格中加些技術性東西,以增加部落格的可讀性和趣味性。讓親愛的讀者們在快樂中就把知識學習了

進階(3)之Handler/Looper/MessageQueue原始碼分析以及原理理解

前言 安卓系統是訊息驅動的,所以深刻了解Handler整個訊息分發機制,對於我們瞭解安卓系統,是一個必不可少的知識點。整個過程中,我們需要重點關注的類是: 1. Handler 2. Looper 3. MessageQueue 4. Meesage 5. Th

動手分析仿QQ聯系人列表TreeView控件

code children cas tail pri bstr abstract viewgroup teset 因項目需要需要用到仿QQ聯系人列表的控件樣式,於是網上找到一個輪子(https://github.com/TealerProg/TreeView)

《結對-結對編項目作業名稱-需求分析》猜拳遊戲

裁判 計算 需求 遊戲 分析 項目 code div 情況 根據需求 分析一下對象,可分析出:玩家對象(Player)、計算機對象(Computer)、裁判對象(Judge)。 玩家出拳由用戶控制,使用數字代表:0石頭、1剪子、2布 計算機出拳由計算機隨機產生 裁判根據玩家