跳一跳小外挂(附完整代码)

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)

 

大功告成!!

谢谢各位。。

posted @ 2018-06-29 00:14  WoLykos  阅读(6819)  评论(7编辑  收藏  举报