1. 程式人生 > >微信跳一跳遊戲助手

微信跳一跳遊戲助手

開頭附上java專案原始碼下載地址

http://download.csdn.net/download/ou775968876/10199335

專案需要java環境和adb環境 不知道配的同學可以轉到這個地址 http://blog.csdn.net/ou775968876/article/details/79028408

功能簡介

用JAVA自動控制手機玩跳一跳

  • 自動識別影象計算距離
  • 自動幫你點選螢幕
  • 自動快取圖片,並在圖片上標記一些識別結果,如下圖示例圖片

執行環境

  1. JAVA,最低版本為7.0,
    官網下載
  2. 安卓手機,目前已適配解析度
    • 1600x2560
    • 1440x2560
    • 1080x1920
    • 720x1280

使用方法

有JAVA開發工具的同學可以直接執行java程式碼,便於程式碼除錯,下面主要介紹執行已經打包好的jar包的方法

  1. 手機開啟USB除錯,並連線電腦

    • 開啟USB除錯方法,進入設定,找到開發者選項,開啟並勾選USB除錯
    • 如果沒有開發者選項,進入關於手機,連續點選版本號7次,即可開啟開發者選項
  2. 通過下面的命令,執行Android.jar

    java -jar Android.jar
    
  3. 根據手機解析度選擇跳躍係數,目前已適配機型:

    • 1600x2560機型推薦0.92
    • 1440x2560機型推薦1.039
    • 1080x1920機型推薦1.392
    • 720x1280機型推薦2.078

    其他解析度請自己微調。

原理說明

  1. 通過adb命令控制手機截圖,並取回到本地

    adb shell screencap -p /sdcard/screen.png
    adb pull /sdcard/screen.png .
    
  2. 圖片分析

    • 根據棋子的顏色,取頂部和底部的特徵畫素點,在截圖中進行匹配,找到棋子座標

    • 由於目標物體不是在左上就是在右上,可以從上往下掃描,根據色差判斷目標物體位置,其中又分為以下幾種型別

      • 有靶點,即目標物體中心的白色圓點,則靶點中心為目標落點

      • 無靶點,但是純色平面,或者規則平面,則平面中心為目標落點

      • 無靶點,又無純色規則平面,但是左上和右上位置的斜率是固定的,可根據固定斜率的斜線和目標物體中心線的焦點計算落點

    • 計算棋子座標和目標落點的距離

    • 距離×跳躍係數=按壓螢幕的時間,不同解析度的手機,跳躍係數也有所不同

  3. 通過adb命令,給手機模擬按壓事件

    adb shell input swipe x y x y time
    

    其中xy是螢幕座標,time是觸控時間,單位ms。

程式碼詳解

這裡將針對一些關鍵演算法的程式碼進行解釋

1. 尋找棋子位置

把截圖放大,可以看到棋子頂部畫素連成一條橫線,那麼我們通過顏色匹配,找到這一條線的始末位置,取中間位置,就得到了棋子的x座標。

棋子的底部也是一條橫線,用顏色匹配,我們檢測到相似顏色的最大y座標,就是棋子底部了,不過考慮到棋子底部是個圓盤,我們把棋子的y座標再往上提一些。

這樣我們就得到了棋子的xy座標,下面是相關程式碼:

/* 計算棋子位置 */
Pixel piece = new Pixel();
for (int i = TOP_BORDER; i < screenHeight - BOTTOM_BORDER; i++) {
    int startX = 0;
    int endX = 0;
    for (int j = LEFT_BORDER; j < screenWidth - RIGHT_BORDER; j++) {
        int red = Color.red(pixels[i][j].color);
        int green = Color.green(pixels[i][j].color);
        int blue = Color.blue(pixels[i][j].color);
        if (50 < red && red < 55
                && 50 < green && green < 55
                && 55 < blue && blue < 65) {//棋子頂部顏色
            //如果偵測到棋子相似顏色,記錄下開始點
            if (startX == 0) {
                startX = j;
                endX = 0;
            }
        } else if (endX == 0) {
            //記錄下結束點
            endX = j;

            if (endX - startX < PIECE_TOP_PIXELS) {
                //規避井蓋的BUG,畫素點不夠長,則重新計算
                startX = 0;
                endX = 0;
            }
        }
        if (50 < red && red < 60
                && 55 < green && green < 65
                && 95 < blue && blue < 105) {//棋子底部的顏色
            //最後探測到的顏色就是棋子的底部畫素
            piece.y = i;
        }
    }
    if (startX != 0 && piece.x == 0) {
        piece.x = (startX + endX) / 2;
    }
}
//棋子縱座標從底部邊緣調整到底部中心
piece.y -= PIECE_BOTTOM_CENTER_SHIFT;

2. 尋找靶點

所謂靶點,就是目標物體中心的那個小圓點,顏色值為0xf5f5f5

那麼我們只需要尋找顏色值為0xf5f5f5的色塊就可以了,為了規避其他物體相近顏色干擾,我們可以限制色塊的大小,正確大小的色塊才是靶點。

但是如何計算色塊的大小呢,色塊最頂端到最底端y座標的差值我們作為色塊的高度,同理,最左側到最右側x座標的差值作為寬度,我們只需要查詢這四個頂點的座標就可以了。

本來打算用凸包的Graham掃描演算法,後來發現色塊已經是凸包了,且邊緣畫素是連續的,那麼我們按照一定順序,遍歷邊緣畫素,就可以在O(n^-2)的時間複雜度裡,得到色塊的頂點座標了。

我們從第一個畫素點開始,尋找的順序如圖所示:

    /**
     * 尋找色塊頂點畫素
     */
    public static final Pixel[] findVertexs(Pixel[][] pixels, Pixel firstPixcel) {
        Pixel[] vertexs = new Pixel[4];
        Pixel topPixel = firstPixcel;
        Pixel leftPixel = firstPixcel;
        Pixel rightPixel = firstPixcel;
        Pixel bottomPixel = firstPixcel;
        Pixel currentPixcel = firstPixcel;
        //先把座標置於左上角
        while (checkBorder(pixels, currentPixcel)//判斷是否超出影象邊緣
                && Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {//判斷是否是相同顏色
            currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];
        }
        while (checkBorder(pixels, currentPixcel)
                && Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {
            currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];
        }
        //尋找上頂點畫素
        while (checkBorder(pixels, currentPixcel)) {
            if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];
            } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1];
            } else {
                topPixel = findCenterPixcelHorizontal(pixels, currentPixcel);
                break;
            }
        }
        //尋找右頂點畫素
        while (checkBorder(pixels, currentPixcel)) {
            if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1];
            } else if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x];
            } else {
                rightPixel = findCenterPixcelVertial(pixels, currentPixcel);
                break;
            }
        }
        //尋找下頂點畫素
        while (checkBorder(pixels, currentPixcel)) {
            if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x];
            } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];
            } else {
                bottomPixel = findCenterPixcelHorizontal(pixels, currentPixcel);
                break;
            }
        }
        //尋找左頂點畫素
        while (checkBorder(pixels, currentPixcel)) {
            if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1];
            } else if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {
                currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x];
            } else {
                leftPixel = findCenterPixcelVertial(pixels, currentPixcel);
                break;
            }
        }
        vertexs[0] = leftPixel;
        vertexs[1] = topPixel;
        vertexs[2] = rightPixel;
        vertexs[3] = bottomPixel;
        return vertexs;
    }

得到了四個座標點,我們就可以計算色塊的中點了,也就是目標落點。

對於沒有靶點,但是落點是規則平面的,也可以用類似演算法。

3. 斜率計算

對於沒有靶點,又不是規則平面的,我們怎麼計算落點呢,這時候就要用到斜率了。

可以看得出來,每次左上角或右上角出現的物體,針對當前物體的方向都是一樣的,也就是兩個物體中心的連線,斜率是固定的。

基本所有的目標物體,最頂點畫素中點的x座標,都是在物體中間,我們至少先得到了目標物體x座標了,記為des.x ,接下來要求des.y 。

如上圖所示,計算過程如下:

    斜線的公式為 y=kx+b 
    那麼,在棋子座標上有 piece.y=k*piece.x+b 
    在目標落點座標上有 des.y=k*des.x+b
    代入得到 des.y=k*(des.x-piece.x)+piece.y

然而這種演算法還是有偏差的。

可以看到,同樣的斜率,如果棋子的位置有偏差,計算出來最終落點還是會有偏差的。

程式碼解析就先講這麼多,希望有大神可以提出更好的解決方案。

玩遊戲小竅門

  1. 連續的落到物體中心位置,是有分數加成的,最多跳一次可以得幾十分
  2. 井蓋、商店、唱片、魔方,多停留一會,有音樂響起後也是有分數加成的

那麼看一下程式設計師的朋友圈有多殘酷吧