微信跳一跳辅助原理浅析

教你自己实现跳一跳辅助

Posted by 梧桐和风的博客 on January 7, 2018

微信跳一跳辅助原理浅析

前言

本文从原理和算法的角度(参考https://github.com/wangshub/wechat_jump_game的实现)探讨怎样实现跳一跳的辅助,做到知其然还要只其所以然。尽量使一个没任何外挂经验的任何语言的普通人也能做出辅助来。当然如果你只打算刷分的话,那本文可能没什么帮助了。(另外,本教程只针对android)

原理介绍

原理其实很简单,棋子跳跃的时间和距离有关。那就利用adb(Android Debug Bridge)将当前手机的截图下载到电脑上,对截图进行分析,算出棋子和要跳的方块之间的距离,再乘以适当的参数即可得到时间。再利用adb模拟手机触摸事件,触摸对应的时间即可。

有2点需要明白的关键:

  1. 什么是adb?
  2. 怎样根据图片算出距离

这2点也是实现辅助的关键,下面一一介绍

ADB介绍

adb这东西应该算android的概念,全称Android Debug Bridge,翻译过来android调试桥?反正是与android设备(如手机)交互的工具。可使用它调试手机应用(需要手机授权)。如安装一个APP,模拟点击事件等,下面列出几个与本文有关的命令。(详情参考Android adb


adb shell screencap /sdcard/screen.png #屏幕截图并储存

adb pull /sdcard/screen.png # 将指定位置图片拉取过来

# 向设备发送模拟操作(输入、触摸等)
adb shell input 
usage:input text <string>  # 输入文字
      input keyevent <key code number or name> # 按键
      input tap <x> <y>    # 点击
      input swipe <x1> <y1> <x2> <y2> [duration(ms)] # 滑动

模拟操作的只用到了 adb shell input swipe <x1> <y1> <x2> <y2> [duration(ms)],表示从(x1,x2)的位置滑动到(x2,y2)的位置,滑动时间为duration毫秒。

怎样算出距离

图片拿到了,怎样算出距离是个难题。在介绍算法之前,有必要先了解下计算机是怎样储存和表示图片信息的。

计算机将图片的颜色表示为RGBA值。我们知道颜色的三原色红绿蓝,RGB即分别表示红绿蓝。剩下的A表示透明度。每个GRBA值表示一个像素,每个图片就是由这些千千万万个像素点构成的。

同时,为表示每个像素点,引入了坐标概念。以左上顶点为原点(0,0),向右为x轴,向下为y轴,像素点坐标均为正值(与数学上不太一致)

如上图所示,我们要想找到棋子,需要充分利用棋子颜色的这个特征,一行一行遍历像素点,直到找到棋子底座颜色(深紫色的)的若干像素点(实际是一个颜色范围区间),平均后得到底座中心位置。

至于方块的位置就不好找了,可以利用色差来做。如上图,背景色是浅黄色,要跳的方块是深灰色,可从上到下扫描像素点,记录背景色,一旦发现有与背景色相差太大的像素点,即表示发现了目标方块,照着此颜色多找几个点,平均下得到方块的中心点。(也可直接用其他方法或用其他方式选取中点,看你自己研究了)

不过这样会有点问题,万一棋子高度大于目标方块就会有误,这在奶茶杯的方块会遇到。这时将棋子的颜色排除掉即可。总之原则就是根据色差找到目标中点。

具体实现

上面的介绍大致就知道了实现原理,由此可知重点是根据图片找到适合的中点。原则上讲只要有处理图片及像素的库的语言都可实现。python、go、java等均可。python处理图片更简单,下面用python简要实现。

寻找棋子中点


 width, height = image.size  # image为图片实例
    num = 0
    sum_x = 0
    sum_y = 0
    s_height = int(height / 3)  # 查找的起始高度
    e_height = int(height / 5 * 4)  # 查找的终止高度

    for i in range(0, width, 10):  # 宽度查找,步长为10
        for j in range(s_height, e_height, 20):  # 高度查找,步长为20
            pixel = image.getpixel((i, j))  # 获取像素点
            # print(pixel)
            if (0x20 < pixel[0] < 0x40) \
                    and (0x20 < pixel[1] < 0x40) \
                    and (0x45 < pixel[2] < 0x80):  # 比较是否在紫色棋子的颜色范围
                r, g, b, a = pixel
                # print('颜色是{},{},{}'.format(r, g, b))
                # print('坐标:{},{}'.format(i, j))
                num = num + 1
                sum_x = sum_x + i
                sum_y = sum_y + j
    if num == 0:  # 出错,停止
        return DEFAULT_ERROR_DISTANCE
    avg_x = int(sum_x / num)   # 找出的点平均后得到中点
    avg_y = int(sum_y / num)

    print('棋子坐标为:{},{}'.format(avg_x, avg_y))

寻找方块中点

以下为寻找方块中点,原理是根据色差。具体:记录上一个像素点,当前像素点与上一个的比较, 若相差过大,表明找到了目标方块的第一个像素点(需排除是棋子的可能),记录在select_color里。 以后遍历时当前像素点就与select_color比较,相差不大则表明是目标方块的点,记录。 收集足够多的点或遍历完成后求中点即可。

    pre_px = None
    h_num = 0
    h_sum_x = 0
    h_sum_y = 0
    select_color = None
    l_num = 0

    flag = False

    for j in range(s_h, e_h, 30):
        for i in range(0, width, 30):
            px = image.getpixel((i, j))
            r, g, b, a = px
            cap, l_num = compare_px(pre_px, px, select_color, l_num, i) # 比较
            if not is_spot(px):
                pre_px = px
            if cap:  # 若色差够大
                h_num = h_num + 1
                h_sum_x = h_sum_x + i
                h_sum_y = h_sum_y + j
                # print('颜色是{},{},{}'.format(r, g, b))
                print('寻找的坐标:{},{}'.format(i, j))
                if h_num == 1:
                    select_color = r, g, b, i
            if l_num == 12:  # 找到足够多的点,跳出循环
                flag = True
        if flag:
            break
    if h_num == 0:  # 未找到,重新开始
        return DEFAULT_ERROR_DISTANCE

    h_avg_x = int(h_sum_x / h_num)
    h_avg_y = int(h_sum_y / h_num)
    
    print('棋盘坐标为:{},{}'.format(h_avg_x, h_avg_y))
    
      
    
# 比较函数
def compare_px(pre, cur, select_cl, like_num, x):
    if pre is None:  # 第一次查找
        return False, like_num
    else:
        pr, py, pb, pa = pre
        r, y, b, a = cur
        if select_cl is None:
            if (abs(pr - r) + abs(py - y) + abs(pb - b) > DEFAULT_GAP) \
                    and not is_spot(cur):  # 若与上一个点色差过大且不是棋子
                return True, like_num + 1  # 目标色点还未赋值,表明第一次发现色点
            return False, like_num  # 色差过大,未找到
        else:  # 则与目标色点比较,若相似且距离合适则表明又发现了一个色点
            sr, sy, sb, sx = select_cl
            if abs(sr - r) < DEFAULT_GAP \
                    and abs(sy - y) < DEFAULT_GAP \
                    and abs(sb - b) < DEFAULT_GAP \
                    and abs(sx - x) < DEFAULT_DISTANCE:
                like_num += 1
                return True, like_num
            return False, like_num  # 相差过大,不是上次选中的色点

后记

我将其简要实现放到了github上,有详细的注释及说明,地址在微信跳一跳辅助,感兴趣的同学可以去看看。一起谈探讨更好的实现。

参考

  1. python 微信《跳一跳》辅助
  2. Python编程快速上手 P333
  3. Android adb介绍