跳一跳 python脚本 改进版

原版本github地址:https://github.com/wangshub/wechat_jump_game

当时版本我用时感觉性能不佳,为能霸榜装逼,针对自己的手机进行了改进。

主要是对检测棋子,棋格部分进行了改进,仍有许多bug和未解决的问题之处,若有兴趣也可以再次进行改进,提升性能。

源码地址:http://download.csdn.net/download/wz2671/10259178

具体的环境配置等,可以参考原作者的github。


jump.py

# -*- coding: utf-8 -*-

"""
=== 思路 ===
核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
    根据两个点的距离乘以一个时间系数获得长按的时间
识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条
    直线,就从上往下一行一行遍历,比较颜色(颜色用了一个区间来比较)
    找到最上面的那一行的所有点,然后求个中点,求好之后再让 Y 轴坐标
    减小一定高度从而得到中心点的坐标
识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,
    由于圆形的块最顶上是一条线,方形的上面大概是一个点,所以就
    用类似识别棋子的做法多识别了几个点求中点,这时候得到了块中点的 X
    轴坐标,利用链码思想求得右边界的X轴坐标,乘以一定系数得到中心点坐标
最后:根据两点的坐标算距离乘以系数来获取长按时间
"""
from __future__ import print_function, division
import os
import sys
import time
import math
import random
from PIL import Image
from six.moves import input
try:
    from common import debug, config, screenshot
except Exception as ex:
    print(ex)
    print('请将脚本放在项目根目录中运行')
    print('请检查项目根目录中的 common 文件夹是否存在')
    exit(-1)


VERSION = "1.1.2"

# DEBUG 开关,需要调试的时候请改为 True,不需要调试的时候为 False
DEBUG_SWITCH = False
#1.392

# Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需
# 设置,设置保存在 config 文件夹中
config = config.open_accordant_config()
under_game_score_y = config['under_game_score_y']
# 长按的时间系数,请自己根据实际情况调节
press_coefficient = config['press_coefficient']
# 二分之一的棋子底座高度,可能要调节
piece_base_height_1_2 = config['piece_base_height_1_2']
# 棋子的宽度,比截图中量到的稍微大一点比较安全,可能要调节
piece_body_width = config['piece_body_width']


def set_button_position(im):
    """
    将 swipe 设置为 `再来一局` 按钮的位置
    """
    global swipe_x1, swipe_y1, swipe_x2, swipe_y2
    w, h = im.size
    left = int(w / 2)
    top = int(1584 * (h / 1920.0))
    left = int(random.uniform(left-50, left+50))
    top = int(random.uniform(top-10, top+10))    # 随机防 ban
    swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, left, top


def jump(distance):
    """
    跳跃一定的距离
    """
    press_time = distance * press_coefficient
    # press_time = max(press_time, 200)   # 设置 200ms 是最小的按压时间
    # 离近了补一点,离远了减一点
    press_time = int(press_time)
    if press_time < 500:
        press_time += 100 - 2*(press_time//10)
    elif press_time > 1000:
        press_time -= press_time//50
    cmd = 'adb shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(
        x1=swipe_x1,
        y1=swipe_y1,
        x2=swipe_x2,
        y2=swipe_y2,
        duration=press_time
    )
    print(cmd)
    os.system(cmd)
    return press_time


def find_piece_and_board(im):
    """
    寻找关键坐标
    """
    w, h = im.size

    scan_x_border = int(w / 8)  # 扫描棋子时的左右边界
    scan_end_y = 0  # 扫描的起始 y 坐标
    im_pixel = im.load()
    # 以 50px 步长,尝试探测 scan_start_y
    for i in range(int(h / 3), int(h*2 / 3), 50):
        last_pixel = im_pixel[0, i]
        for j in range(1, w):
            pixel = im_pixel[j, i]
            # 不是纯色的线,则记录 scan_start_y 的值,准备跳出循环
            if sum(list(map(lambda x: abs(x[0]-x[1]), zip(pixel, last_pixel)))) > 3:
                scan_end_y = i
                break
        if scan_end_y:
            break

    # 记录下y,对该行扫描,确定终点的横坐标
    # 由于圆左右不对称,回退寻找最顶端的那条线
    pure = False
    for i in range(scan_end_y, scan_end_y-55, -1):
        if pure:
            scan_end_y = i+2        # 要加2,上一条已经是纯白线,但又减了一次
            break
        last_pixel = im_pixel[0, i]
        for j in range(1, w):
            pixel = im_pixel[j, i]
            if sum(list(map(lambda x: abs(x[0]-x[1]), zip(pixel, last_pixel)))) > 3:
                break
        if j == w-1:
            pure = True

    # 从 scan_end_y 开始往下扫描,棋子应位于屏幕下半部分,这里暂定不超过 2/3
    scan_start_y = 0
    for i in range(scan_end_y, int(h * 2 / 3)):
        # 横坐标方面也减少了一部分扫描开销
        for j in range(scan_x_border, w - scan_x_border):
            pixel = im_pixel[j, i]
            # 根据棋子的颜色判断
            if (51 < pixel[0] < 54) \
                    and (51 < pixel[1] < 63) \
                    and (58 < pixel[2] < 64):
                scan_start_y = i
                break
        if scan_start_y != 0:
            break
    # 计算棋子的横坐标
    start_x = 0
    piece_x_c = piece_x_sum = 0
    for i in range(scan_x_border, w - scan_x_border):
        pixel = im_pixel[i, scan_start_y]
        if (51 < pixel[0] < 54) \
                and (51 < pixel[1] < 63) \
                and (58 < pixel[2] < 64):
            piece_x_sum += i
            piece_x_c += 1
    start_x = piece_x_sum//piece_x_c

    # 计算棋格的最顶端坐标,考虑棋子比棋格高的情况
    # 因为棋子和棋格在中线两侧,所以从另外的一边找棋格顶端
    st = ed = 0
    dir = 0     # 鉴别方向,-1朝左跳, 1朝右跳
    if start_x < w/2:
        st = w//2+1
        ed = w
        dir = 1
    else:
        st = 1
        ed = w//2
        dir = -1
    pure = True
    if scan_end_y == scan_start_y:   # 当棋子比棋格低,继续往下找
        for i in range(scan_end_y, int(h * 2 / 3)):
            last_pixel = im_pixel[0, i]
            for j in range(st, ed):
                pixel = im_pixel[j, i]
                # 不是纯色的线,则记录 scan_end_y 的值,准备跳出循环
                if sum(list(map(lambda x: abs(x[0]-x[1]), zip(pixel, last_pixel)))) > 3:
                    pure = False
                    scan_end_y = i
                    break
            if pure != True:
                break

    end_x_c = end_x_sum = 0
    pixel = im_pixel[st-1, scan_end_y]

    for i in range(st, ed):
        last_pixel = im_pixel[i, scan_end_y]
        if sum(list(map(lambda x: abs(x[0]-x[1]), zip(pixel, last_pixel)))) > 3:
            end_x_sum += i
            end_x_c += 1
    end_x = end_x_sum//end_x_c

    if not all((piece_x_sum, piece_x_c)):
        return 0, 0, 0, 0

    # scan_end_y 棋盘最上方的高度, scan_start_y 棋子最上方的高度
    # 棋子最终坐标 start_x(已求得), start_y
    # 棋盘最终坐标 end_x(已求得), end_y

    # 棋子纵坐标可以通过顶端坐标减去到中心的距离求得
    start_y = scan_start_y + 191  # 191是我量出来的,只针对本手机的分辨率情况下

    # 求棋盘的纵坐标end_y
    # 根据棋子跳跃方向决定检测边界的方向,借鉴链码的思想
    # 连续向下超过5即为边界
    down_time = 0
    bac_pixel = im_pixel[end_x, scan_end_y-1]
    # 以向右跳为例,分别代表右,右下,下, 左下, 左; 这儿原先想根据跳跃方向向对应方向检测,但是左边会受阴影影响
    # 向右检测暂时未出现被遮挡不能检测的情况,只出现过棋子跳出边界的情况,这种情况应该很少见,不管了
    step = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]]
    dx = end_x; dy = scan_end_y
    back = False
    # 考虑到带耳朵的方块出现的一些特殊情况,加了向左下,左方向的判断
    while(down_time < 5):
        for i in range(6):
            if (back and i == 0) or (back and i == 1):
                continue
            next_pixel = im_pixel[dx+step[i][0], dy+step[i][1]]
            if sum(list(map(lambda x: abs(x[0]-x[1]), zip(next_pixel, bac_pixel)))) > 20:
                dx += step[i][0]
                dy += step[i][1]
                if i == 2:
                    down_time += 1
                elif down_time != 0:
                    down_time = 0

                if i == 4 or i == 5:
                    back = True
                elif back:
                    back = False

                break
    # 这个0.57是指中心点到上边界和右边界的距离比,圆和正方形的比值不同,圆的更小些,正方形更大些,
    # 暂未想出合适的区分方法,可以通过上面求出的链码求面积或周长进行判断,不过略麻烦
    end_y = scan_end_y + abs(int(0.57*(dx-end_x)))  # 0.57以实际机型为准

    return end_x, end_y, start_x, start_y


def yes_or_no(prompt, true_value='y', false_value='n', default=True):
    """
    检查是否已经为启动程序做好了准备
    """
    default_value = true_value if default else false_value
    prompt = '{} {}/{} [{}]: '.format(prompt, true_value,\
        false_value, default_value)
    i = input(prompt)
    if not i:
        return default
    while True:
        if i == true_value:
            return True
        elif i == false_value:
            return False
        prompt = 'Please input {} or {}: '.format(true_value, false_value)
        i = input(prompt)


def main():
    """
    主函数
    """

    # debug.dump_device_info()
    screenshot.check_screenshot()

    while True:
        screenshot.pull_screenshot()
        im = Image.open('./autojump.png')
        # 获取棋子和 board 的位置
        end_x, end_y, start_x, start_y = find_piece_and_board(im)
        # print(start_x, start_y, end_x, end_y)
        set_button_position(im)
        jump(math.sqrt((end_x-start_x)**2+(end_y-start_y)**2))
        # if DEBUG_SWITCH:
        #     debug.save_debug_screenshot(ts, im, piece_x,
        #                                 piece_y, board_x, board_y)
        #     debug.backup_screenshot(ts)
        im.close()

        # 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
        time.sleep(random.uniform(1.0, 1.2))


if __name__ == '__main__':
	main()

游戏效果图如下:


然而,,,


不过低调些应该还是不会被检测出的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值