0x1、引言

春节前最后一周,留守的搭档都在摸鱼,杰哥也是如此,百无聊赖中点开了一个小游戏。本想着打发时刻,成果被其中一个 “孤岛求生” 的玩法给拿捏了:

Van♂Python | 手搓游戏

这不当妥滴 2D版的微信跳一跳?玩法也很简略:

  • 长按建造生成木板 (按得越久木板越长);
  • 松开按钮,木板放下,搭到另一个石头上,人借着木板挪到下一块石头;
  • 木板过短或者过长,人就会掉到水里,游戏完毕!(当然,氪金是能够复活滴~)

手残杰哥就没越过20+,每次都是差一点点,死了几次,气到想砸手机,焯!

Van♂Python | 手搓游戏

这哪能忍啊?有道是 科技才是第一生产力,得想方法整个 辅助!搜了一圈没有找到现成能用的,估摸着是冷门氪金小游戏,玩的人不多。又看了下微信跳一跳的脚本,场景不同,改的话基本是重新写了。索性,就自己手搓一个吧!

思路分析

把上面的玩法简略抽象下,其实便是 小学三四年级的常见数学问题《速度、时刻与旅程间的联系》,怎么说?

  • 速度 → 木板变长的速度,单位:像素/毫秒
  • 时刻 → 手指按压按钮的时刻,单位:毫秒
  • 旅程/间隔 → 生成木板的长度,单位:像素

而这儿很明显便是 求时刻,想方法收集到 旅程和速度,一相除,答案就出来了~

Van♂Python | 手搓游戏

行吧,有思路了,直接开整!!


0x2、收集旅程

适宜的木头长度便是 两块石头间的间隔,而人物会一直处于石头的中心,所以更精确的描述是:两个石头中心点的间隔,即图中黄线:

Van♂Python | 手搓游戏

接着是石头中心点的确认,它也是 数字编号的中心点,所以这儿的思路是 识别出编号的区域,然后求出中心点

数字文字?我测验用 《破大防!这个开源库,竟能让APP日常使命主动化变得如此简略》 说到的OCR库-chineseocr_lite 进行识别:

Van♂Python | 手搓游戏

em…并没有识别出数字1,那就只能自己来处理图片了,把图片中数字部分扩大:

Van♂Python | 手搓游戏

数字区域是白色的 能够测验 灰度二值化 处理下,只重视数字区域,先裁剪下图片,提高处理速度:

def crop_area(pic_path, save_dir, start_x, start_y, end_x, end_y):
    """
    裁剪图片
    :param save_dir: 保存途径
    :param pic_path: 图片途径
    :param start_x: x轴起始坐标
    :param start_y: y轴起始坐标
    :param end_x: x轴终点坐标
    :param end_y: y轴终点坐标
    :return: 生成的截图途径
    """
    img = Image.open(pic_path)
    region = img.crop((start_x, start_y, end_x, end_y))
    save_path = os.path.join(save_dir, "crop_" + str(round(time.time() * 1000)) + ".png")
    region.save(save_path)
    print(save_path)
    return save_path
crop_pic = crop_area(screenshot_pic, temp_dir, 0, 1260, 1080, 1338)

裁剪后的图片:

Van♂Python | 手搓游戏

接着灰度二值化一下,试了几次,发现240比较适宜:

def picture_to_black_white(pic_path, save_dir, threshold=127):
    """
    图片二值化(是非)
    :param save_dir:
    :param pic_path: 图片途径
    :param threshold:  灰度阈值,默许127
    :return: 转换后的图片途径
    """
    img = Image.open(pic_path)
    save_path = os.path.join(save_dir, "bw_" + str(round(time.time() * 1000)) + ".jpg")
    if threshold == 127:
        img.convert('1').save(save_path)
    else:
        img.convert('L').point([0 if x < threshold else 1 for x in range(256)], '1').save(save_path)
    return save_path
bw_pic = picture_to_black_white(crop_pic, temp_dir, 240)

处理后的图片:

Van♂Python | 手搓游戏

能够,作用很Nice,紧接着便是检测文字区域了,这儿有两个思路~

思路①:卷积运算进行边际检测

学习《python完成图画边际检测》,采用 卷积运算 来进行边际检测,详细方法如下:

Van♂Python | 手搓游戏

看不太懂?没联系,直接CV代码:

# 求卷积
def convolution(img, x, y):
    convolution_list = [1, 1, 1, 1, -8, 1, 1, 1, 1]  # 卷积核列表
    color_list = []
    xl = [x - 1, x, x + 1]
    yl = [y - 1, y, y + 1]
    for j in yl:
        for i in xl:
            color = img.getpixel((i, j))  # 取出色值
            color_list.append(color)
    c = 0
    for i, j in zip(convolution_list, color_list):
        c = c + i * j
    return c
# 边际检测
def edge_detection(origin_pic):
    origin_img = Image.open(origin_pic)
    w, h = origin_img.size
    new_img = Image.new('L', (w, h), "white")
    for x in range(1, w - 1):
        for y in range(1, h - 1):
            c = convolution(origin_img, x, y)
            if c > 0:
                s = 0
            else:
                s = 255
            new_img.putpixel((x, y), s)
    new_img.save("test_n.png")

运转看看:

Van♂Python | 手搓游戏

em?如同不太对劲,没有想象中的框,一开端还以为是算法问题,后面看了下二值化后的图片,发现竟然有0和255外的色值:

Van♂Python | 手搓游戏

搜了下有人说是PIL模块的point()二值化不彻底,所以我遍历每个元素手动设置了一波,后面发现也是如此。折腾了好一会儿,才找到了问题的根源:

保存的图片格式为 .jpg,默许清晰度是75,有损紧缩导致呈现 马赛克。清晰度能够经过 quality 参数指定,1(最差)到95(最佳),使用时应尽量防止高于95的值,100会禁用部分JPEG紧缩算法,并导致大文件图画质量几乎没有任何增益。

Van♂Python | 手搓游戏

说的很好,我挑选保存成 .png 格式,再次运转:

Van♂Python | 手搓游戏

牛批,真的把数字的轮廓抠出来了,接着便是核算中心点咯,找到两个数字各自的左右x坐标,求平均值:

Van♂Python | 手搓游戏

edge_detection() 的循环中,加个 Set (自带去重),把边际的点的x坐标都丢进去。

接着转化为List,升序排列,列表头尾元素 分别为第一个数字的左面第二个数字的右边。从左到右遍历,如果前后两个x坐标相差大于50,阐明前一个元素是 第一个数字的右边,后一个元素是 第二个数字的左面,得到四个x坐标,依次求出两中心点和间隔,详细代码如下:

Van♂Python | 手搓游戏

运转输出成果:

Van♂Python | 手搓游戏

卷积运算进行边际检测能够,接着来试试第二个思路。


思路②:土方法无脑遍历坐标点

还是那个二值化后的图片,图中的数字有两个:

Van♂Python | 手搓游戏

早年和后开端扫,能够快速得出:第一个数字的左面坐标第二个数字的右边坐标,然后 取中点,一个向前,一个向后扫,能够快速得出 第一个数字的右边坐标第二个数字的左面坐标,这样能够减少无效遍历次数,直接写出详细代码:

Van♂Python | 手搓游戏

运转输出成果如下:

Van♂Python | 手搓游戏

虽然x坐标和卷积核算检测出来的成果差1,但终究核算出的 间隔是相同的,为什么差1,读者能够自己考虑下~

Van♂Python | 手搓游戏

行吧,旅程的测量方法已经get√到了,接着到速度~


0x3、收集速度

这个就简略些了,控制变量固定长按的时刻,比如:100ms,然后 按压前截图按压后截图,核算两块木板的长度差,除以100ms,就能拿到 木板变长的速度

Van♂Python | 手搓游戏

看到这儿估量有些童鞋会收:你仿佛在逗我,来,给你个秒表,按压个100ms我看看?

em…手动肯定是不行的,但是 主动 能够哇,利用在 《杰哥带你玩转Android主动化》 专栏学到的姿势就阔以~

Van♂Python | 手搓游戏

先是 长按屏幕,ADB中并 没有直接供给长按屏幕的指令,但是咱们能够经过 滑动来模拟长按。当 滑动前后两个坐标差值满足小,android系统就会以为咱们进行了长按操作。直接写出东西代码:

def long_click_xy(x, y, duration):
    return start_cmd('adb shell input swipe %d %d %d %d %d' % (x, y, x + 1, y + 1, duration))

接着便是点击坐标的确认,直接翻开 开发者形式 → 找到 指针位置 → 翻开开关,此时点击屏幕,就能看到接触点的XY坐标了~

Van♂Python | 手搓游戏

行吧,接着代码主动生成按压前后的截图了:

def get_speed():
    long_click_xy(546, 1774, 100)
    sleep(0.1)
    screenshot(temp_dir)

运转一下

Van♂Python | 手搓游戏

100ms有些快了,这都要掉水里了,并且截图也是需求时刻的(不是实时的),改成500ms看看吧,也不休眠,直接截图:

Van♂Python | 手搓游戏

行吧,截图拿到了,接着量下木板有多长,即 木板起点到终点间隔多少个像素,这儿也不整些花里花哨的,直接 PS 取。

电脑没装,直接搜 photoshop在线网页版,然后翻开截图,右键吸管,找到 标尺东西

Van♂Python | 手搓游戏

点击起点拖拽到终点,右上角就能拿到 木板的终究长度

Van♂Python | 手搓游戏

然后其实状况的截图也是这样操作,拿到 木板的初始长度

Van♂Python | 手搓游戏

两个长度相减,然后除以500ms,终究得出木板的变长速度约为:86px/100ms


0x4、核算时刻

间隔和速度都获取到了,接着便是相除求时刻了。别的,这个棍子并不是居中的,核算出间隔后还要减去棍子的偏移值。代码如下:

# 求出间隔
distance = second_center_x - first_center_x - 55
# 除以速度,求出按压时刻
duration = int(float(distance / 86) * 100)
# 模拟按压
long_click_xy(546, 1774, duration)

加个循环,调用上述代码,运转看下作用:

Van♂Python | 手搓游戏

控制台输出信息如下:

Van♂Python | 手搓游戏

有些朋友看到这儿估量还不信:这是录屏,并且就前四关,不会是你自己玩然后装成脚本在玩吧?

Van♂Python | 手搓游戏

Van♂Python | 手搓游戏

这波石锤了,杰哥真的是玩游戏开 “辅助“,从现在开端这儿叫做XXX广场!!!2333,消费一下~

Van♂Python | 手搓游戏

行吧,就折腾到这儿,对源码感兴趣的能够移步至:sssf.py 自行检查,谢谢,也提早祝各位读者 春节快乐,阖家健康~

Van♂Python | 手搓游戏