跳一跳小外掛(附完整代碼)


1實驗環境

操作系統:Windows10

編碼語言:Python3.6

編譯平台:Pycharm

Python庫:os、datetime、matplotlib、opencv-python、time

 

2實驗代碼流程圖

  

 

3代碼運行步驟和結果等

3.1 手機和電腦用數據線連接

使用通過數據線連接手機,將開發者模式打開並授權

通過adb命令

adb devices

可以查看連接的Android設備的信息

 

3.2 獲取手機相關的信息

查看Android手機的分辨率(第四行)

adb shell dumpsys window displays

 

獲取屏幕密度

adb shell wm density

 

獲取手機型號

adb shell getprop ro.product.device

獲取Android系統的版本

adb shell getprop ro.build.version.release

 

3.3 截屏

輸入如下命令:

adb shell screencap -p /sdcard/auto.png

此時,截屏的圖片就保存到 /sdcard/auto.png文件中。

注意:/sdcard/和/data/目錄是可以寫入的。

 

可以通過命令查看sdcard目錄下所有的文件。

adb shell ls /sdcard/ -l

 

通過如下命令把手機上的文件拷貝到電腦上

adb pull /sdcard/auto.png h:\

此時,圖片就會被拷貝到h:\根目錄下了。打開即可看到當前手機的屏幕信息。

 

3.4 屏幕點擊事件

通過如下命令模擬手機的滑動事件

adb shell input swipe x1 y1 x2 y2 duration

通過adb shell input swipe命令進行滑動

l  x1、y1:滑動開始的點。

l  x2、y2:滑動結束的點。

l  duration:持續的時間(單位ms)。

特殊情況下:如果不寫duration參數,就理解為點擊事件。如果寫duration,然后x1y1和x2y2是相同的點,就表示長按。

 

跳一跳關鍵是:duration的值的計算。

嘗試:

adb shell input swipe 100 100 100 100 700

嘗試修改duration的值,看看跳的效果。

求得可以拿到加分的中間值。比如555~871都可以拿到加分(555以下和871以上就不能拿到加分),此時則取中間值為(555+871)/2=713 作為后面計算的參考值。

 

3.5 duration值的計算

假設我們截屏的效果是如下:

 

從圖中可以看到,時間的值跟開始位置到結束位置的距離有關。

假設時間是t,距離是s。公式應該是s = at

基本思路:兩點之間的距離乘以一個時間系數。

所以要從截圖上識別出起跳位置的坐標(x1,y1)和目標位置的坐標(x2,y2)。

起跳位置的坐標:小人的底座中心點

目標位置的坐標:目標菱形的中心點

然后計算這兩點之間的距離(歐氏距離):sqrt((x1-x2)2+(y1-y2)2)

 

3.6 尋找關鍵坐標——起跳坐標

算法策略:獲取小人的底座中心點的值作為起跳點。

1 獲取小人的所有像素點中y坐標的最大值

2 在小人y坐標的最大值那些像素點中,計算出x的平均值,作為小人底座的x的值。

3 y坐標的最大值減去一個偏移值,就作為小人底座的y值。(注意:該偏移值不同的設備是不同的,同一台設備不同場景下是一樣的)

比如教師機的設備中最低點的值是(410,1162),中心值是(410,1142),從而計算出偏移值為1162-1142=20

 

3.7 獲取目標坐標的y值

取屏幕寬和高的一半(x=540和y=960)

 

我們會發現,目標格子的邊沿(x=560,y=980)和這個是差不多的(y的偏差是20,x的偏差是20)

以后每次跳動的時候,假如已經知道目標格子的邊沿,和目標坐標的x值,就可以很輕松計算出目標坐標的y值。

注意:每個格子的寬和高的比例是相同的。

 

方形:左:(560,848)                 園:左:(251,876)

   右:(1015,848)                           右:(522,876)

   上:(790,718)                             上:(388,799)

   下:(790,980)                             下:(388,957)

   中:(790,850)                             中:(388,876)

 

高和寬的比例:(980-718)/(1015-560) =262/455=0.576。假設該值為p

  

最后,由已知的目標坐標的x值,求目標坐標的y值。

 

先附上運行結果,以及截圖信息:

 

 

在理解了跳一跳的基本思路之后,現在附上完整代碼(有注釋):

# main.py
#
_*_ coding:utf-8 _*_ __author__ = 'WoLykos' from operations import * from draw import * from algorithm import * import time import random # 測試截屏 # def test_screen_cap(): # op = Operation() # op.screen_cap() # 測試顯示圖片 def test_show_pic(): draw = Draw() draw.show_pic("img/auto.png") # 測試計算歐式距離 def test_euclidean_distance(): algorithm = Algorithm() p1 = (3, 4) p2 = (6, 8) d = algorithm.euclidean_distance(p1, p2) print(d) # 測試尋找關鍵坐標 def test_find_point(): op = Operation() im = op.screen_cap() algorithm = Algorithm() start_x, start_y, end_x, end_y = algorithm.find_point(im) print("start_point:", start_x, start_y) print("end_point:", end_x, end_y) start_point = (start_x, start_y) end_point = (end_x, end_y) distance = algorithm.euclidean_distance(start_point, end_point) # print(distance) press_time = algorithm.distance_to_time(distance) op.jump(start_point, end_point, press_time) if __name__ == "__main__": # test_screen_cap() # test_show_pic() while True: # test_euclidean_distance() test_find_point() time.sleep(1 + 2*random.random())

 

# algorithm.py
# _*_ coding:utf-8 _*_
__author__ = 'WoLykos'


class Algorithm:
    # 構造器
    def __int__(self):
        pass

    # 計算兩點之間的歐氏距離
    # p1和p2表示兩個點 用元組來表示
    def euclidean_distance(self, p1, p2):
        return ((p1[0]-p2[0])**2+(p1[1]-p2[1])**2)**0.5  # ((p1[0]-p2[0])**2+(p1[1]-p2[1])**2)**0.5

    # 尋找關鍵坐標
    # 返回值1,2 piece_x, piece_y 起跳點的坐標 170,555
    # 返回值3,4 board_x, board_y 目標點的坐標 395,425
    def find_point(self, im):
        # piece_x = piece_y = 0
        # board_x = board_y = 0

        # 圖像的大小
        w, h = im.size  # (1080,1920)
        # 加載圖像
        im_pixel = im.load()
        # 記錄小人所有的點
        points = []

        # 記錄y的最大值
        piece_y_max = 0

        # 1 計算出起跳點 就是小人底座的中心點
        # 1.1 獲取小人的所有像素點中y坐標的最大值
        # 遍歷圖像中的每一個點
        # 遍歷每一行
        for i in range(h // 3, h * 2 // 3):
            # 遍歷每一列
            for j in range(w):

                pixel = im_pixel[j, i]
                # print("i = ", i, ",j = ", j, "pixel = ", pixel)

                # 判斷pixel是否小人所在的位置
                # 當該點的RGB值約為56,56,82的時候就可以認為是小人所在的像素點了
                if (51 < pixel[0] < 61 and 51 < pixel[1] < 61 and 72 < pixel[2] < 102):
                    # 把當前的點添加到points數組中
                    points.append((j, i))  # (x,y)
                    # 記錄下y的值
                    if i > piece_y_max:
                        piece_y_max = i

        # print("piece_y_max = %d" % (piece_y_max,))

        # 1.2 在小人y坐標的最大值那些像素點中,計算出x的平均值,作為小人底座的x的值。
        bottom_x = []
        for x, y in points:
            if y == piece_y_max:
                bottom_x.append(x)

        piece_x = sum(bottom_x) // len(bottom_x)
        # print("piece_x = %d" % (piece_x,))

        # 1.3 y坐標的最大值減去一個偏移值,就作為小人底座的y值。(注意:該偏移值不同的設備是不同的,同一台設備不同場景下是一樣的)
        piece_y = piece_y_max - 20  # 偏移值1130-110=20
        # print("piece_y = %d" % (piece_y,))

        # 2計算 目標格子的中心點
        # 2.1計算目標格子的x值
        points = []
        # 只取中間1/3進行掃描
        for i in range(h // 3, h * 2 // 3):
            if len(points) > 0:
                break
            # 取坐標的一個點作為背景的參照物
            last_pixel = im_pixel[0, i]
            # 逐個掃描右邊的點
            for j in range(w):
                pixel = im_pixel[j, i]
                # 把當前點與最左邊的點比較 如果RGB差異比較大 則認為是目標點
                # 排除該點為小人像素點56,56,82的可能性,BUG
                if not (54 < pixel[0] < 141 and 54 < pixel[1] < 130 and 69 < pixel[2] < 172):
                    if (abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1])
                            + abs(pixel[2] - last_pixel[2]) > 10):
                        points.append((j, i))

        top_x = []
        for x, y in points:
            top_x.append(x)

        board_x = sum(top_x) // len(top_x)
        # print("board_x = %d" % (board_x,))

        # 2.2計算目標格式子y值
        # 屏幕中心的值
        center_x = w / 2 + 20  # x的偏差是20
        center_y = h / 2 + 20  # y的偏差是20,園
        # 格子高和寬的比例
        height_per_width = 262 / 455
        # 計算出目標格子的y值(需要轉換成整數)
        # 從piece_x調到board_x 如果piece_x < board_x則表示從左往右跳
        # 如果piece_x > board_x 則表示從右往左跳
        if piece_x < board_x:
            board_y = int(center_y - height_per_width * (board_x - center_x))
        else:  # 從右往左跳
            board_y = int(center_y + height_per_width * (board_x - center_x))
        # print("board_y = %d" % (board_y,))

        return piece_x, piece_y, board_x, board_y

    # 距離與時間的轉換
    def distance_to_time(self, distance):
        # 當0分的時候 距離為 527.5234591939964 時間為713
        p = 713 / 527.5234591939964  # 該算法后面待優化
        press_time = distance * p
        return press_time

 

# operations.py
# _*_ coding:utf-8 _*_
__author__ = 'WoLykos'

import os
import datetime

from PIL import Image


# 實現控制安卓
class Operation:
    # 構造方法
    def __int__(self):
        pass

    # 截屏
    def screen_cap(self):
        filename = time = datetime.datetime.now().strftime("%H%M%S")+".png"
        # 截屏並保存到手機
        cmd = "adb shell screencap -p /sdcard/auto.png"
        os.system(cmd)
        # 拷貝到電腦
        cmd = "adb pull /sdcard/auto.png "+"img/"+filename
        os.system(cmd)

        # 打開圖像文件
        return Image.open("img/"+filename)

    # 控制屏幕進行跳動
    def jump(self, src, dst, press_time):
        # print(press_time)
        press_time = int(press_time)
        cmd = "adb shell input swipe %d %d %d %d %d" % (
            int(src[0]), int(src[1]),
            int(dst[0]), int(dst[1]),
            press_time
        )
        print(cmd)
        os.system(cmd)

 

大功告成!!

謝謝各位。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM