diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ + diff --git a/README.md b/README.md index d6d99b5f..dcbc8ffb 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,90 @@ -# 教你用Python来玩微信跳一跳 +# 教你用 Python 来玩微信跳一跳 [https://wangshub.github.io](https://wangshub.github.io) -[github项目地址](https://github.com/wangshub/wechat_jump_game) +[GitHub 项目地址](https://github.com/wangshub/wechat_jump_game) -* QQ群 +* QQ 群 * github微信跳一跳 **314659953**(已满) * github微信跳一跳(2) **176740763** ### **更新日志:** * 2017-12-29 : - * 增加更新自动化运行脚本,感谢github上的 [@binderclip](https://github.com/binderclip) + * 增加更新自动化运行脚本,感谢 GitHub 上的[@binderclip](https://github.com/binderclip) * 2017-12-30 : - * 请将安卓手机的usb调试模式打开,设置》更多设置》开发者选项》USB调试,如果出现运行脚本后小人不跳的情况,请检查是否有打开“USB调试(安全模式)” - * 根据大家反馈:1080屏幕距离系数**1.393**,2k屏幕为**1** + * 请将安卓手机的 USB 调试模式打开,设置》更多设置》开发者选项》USB 调试,如果出现运行脚本后小人不跳的情况,请检查是否有打开“USB 调试(安全模式)” + * 根据大家反馈:1080 屏幕距离系数 **1.393**,2K 屏幕为 **1** * 添加部分机型配置文件,可直接复制使用 ### 相关问题 -> 请先查阅一下issue区 +> 请先查阅一下 issue 区 - 参数出错请在这里提交:[issues/62](https://github.com/wangshub/wechat_jump_game/issues/62) -- 如果你是ios参考一下: [issues/99](https://github.com/wangshub/wechat_jump_game/issues/99) 和 +- iOS 相关问题:[issues/99](https://github.com/wangshub/wechat_jump_game/issues/99) 和 [/issues/4](https://github.com/wangshub/wechat_jump_game/issues/4) -- 如果你想自动运行:请运行`wechat_jump_auto.py`,记得修改`/config/default.json`参数(这个是默认的配置) -- 如果你是ios,请运行:`wechat_jump_iOS_py3.py` -- 更新了一些分辨率参数配置,请按照你的手机分辨率从`config/`文件夹找到相应的配置,拷贝到*.py同级目录;(如果屏幕分辨率能成功探测,会直接调用config目录的配置,不需要复制) +- [iOS 苹果手机操作步骤](#ios-%E6%89%8B%E6%9C%BA%E6%93%8D%E4%BD%9C%E6%AD%A5%E9%AA%A4) +- [Android 安卓手机操作步骤](#%E5%AE%89%E5%8D%93%E6%89%8B%E6%9C%BA%E6%93%8D%E4%BD%9C%E6%AD%A5%E9%AA%A4) - 注意:别刷太高,已经有同学遇到分数清零的情况了[164](https://github.com/wangshub/wechat_jump_game/issues/164) - 如果有找不到`./autojump.png`图片的错误,请查阅[194](https://github.com/wangshub/wechat_jump_game/issues/194) +- 小白用户可以参考一个B站UP主的视频教程 [【微信跳一跳】教你如何不用双手还能霸占排行榜第一名](https://www.bilibili.com/video/av17796840/?redirectFrom=h5) ## 游戏模式 -> 2017年12月28日下午,微信发布了 6.6.1 版本,加入了「小游戏」功能,并提供了官方 demo「跳一跳」。 +> 2017 年 12 月 28 日下午,微信发布了 6.6.1 版本,加入了「小游戏」功能,并提供了官方 DEMO「跳一跳」。 这是一个 2.5D 插画风格的益智游戏,玩家可以通过按压屏幕时间的长短来控制这个「小人」跳跃的距离。可能刚开始上手的时候,因为时间距离之间的关系把握不恰当,只能跳出几个就掉到了台子下面。 -玩法类似于《flappy bird》 +玩法类似于《Flappy Bird》 ![](https://ws1.sinaimg.cn/large/c3a916a7gy1fmxe4gnfhnj20hs0a0t8q.jpg) -**如果能精确测量出起始和目标点之间测距离,就可以估计按压的时间来精确跳跃?所以花2个小时写了一个python脚本进行验证** +**如果能精确测量出起始和目标点之间测距离,就可以估计按压的时间来精确跳跃?所以花 2 个小时写了一个 Python 脚本进行验证** 希望不要把分数刷太高,容易没朋友的。。。 +## 操作规范 +> 考虑到生产环境的规范性,实验与项目之间不受干扰,请尽量用新的虚拟环境来完成实验 + +MacOS/Win,请使用如下操作开辟新的虚拟环境(不强调表示MacOS/Win相同操作) +- 下载Anaconda. MacOS:默认安装/Win:注意安装时候勾选配置路径或者之后手动配置,直至cmd后conda关键字有效 +- 查看所有的虚拟环境`conda info --envs` +- 使用命令:`conda create -n wechat_env python=3`,创建名为`wechat_env`的虚拟环境,且配置python版本为python3 +- 激活虚拟环境:MacOS: `source activate wechat_env`/Win:`activate wechat_env` +- 安装所需要的包,比如`matplotlib`等,建议使用`conda install package_name`来避免虚拟环境包的路径问题 + +**接下来的操作非必须,仅当实验完成后可操作,试验阶段全程在虚拟环境中操作,进入虚拟环境会有前置符号表示如:** +``` +(wechat_env) ~/Desktop/wechat_jump_game-master> +``` +- 退出虚拟环境:MacOS: `source deactivate wechat_env` / Win: `deactivate wecha_env` +- 删除虚拟环境: `conda remove -n wechat_env --all` + + ## 工具介绍 - Python - 手机或模拟器 -- [Adb](https://developer.android.com/studio/releases/platform-tools.html) 驱动,可以到[这里](https://adb.clockworkmod.com/)下载 +- [ADB](https://developer.android.com/studio/releases/platform-tools.html) 驱动,可以到[这里](https://adb.clockworkmod.com/)下载 - 相关依赖 -如果你是`iOS`,请参考下面的配置: -- 使用真机调试wda,参考iOS 真机如何安装 [WebDriverAgent · TesterHome](https://testerhome.com/topics/7220) +如果你是`iOS` + MacOS,请参考下面的配置: +- 使用真机调试 WDA,参考 iOS 真机如何安装[WebDriverAgent · TesterHome](https://testerhome.com/topics/7220) - 安装[openatx/facebook-wda](https://github.com/openatx/facebook-wda) - Python 3 +如果你是 `Android` + MacOS,请参考下面的配置: +- Python 3 +- 使用brew进行安装 `brew cask install android-platform-tools` +- 安装完后插入安卓设备且安卓已打开usb调试模式,终端输入 `adb devices` ,显示如下表明设备已连接 +``` +List of devices attached +6934dc33 device +``` + ## 依赖安装 ``` bash @@ -67,7 +94,7 @@ ## 原理说明 1. 将手机点击到《跳一跳》小程序界面; -2. 用Adb 工具获取当前手机截图,并用adb将截图pull上来 +2. 用 ADB 工具获取当前手机截图,并用 ADB 将截图 pull 上来 ```shell adb shell screencap -p /sdcard/autojump.png @@ -75,10 +102,10 @@ ``` 3. 计算按压时间 - * 手动版:用matplotlib显示截图,用鼠标点击起始点和目标位置,计算像素距离; + * 手动版:用 Matplotlib 显示截图,用鼠标点击起始点和目标位置,计算像素距离; * 自动版:靠棋子的颜色来识别棋子,靠底色和方块的色差来识别棋盘; -5. 用Adb工具点击屏幕蓄力一跳; +4. 用 ADB 工具点击屏幕蓄力一跳; ```shell adb shell input swipe x y x y time(ms) @@ -87,20 +114,26 @@ ## 安卓手机操作步骤 -- 安卓手机打开USB调试,设置》开发者选项》USB调试 -- 电脑与手机USB线连接,确保执行`adb devices`可以找到设备id +- 安卓手机打开 USB 调试,设置》开发者选项》USB 调试 +- 电脑与手机 USB 线连接,确保执行`adb devices`可以找到设备 ID - 界面转至微信跳一跳游戏,点击开始游戏 -- 运行`python wechat_jump_auto.py`,如果手机界面显示USB授权,请点击确认 +- 运行`python wechat_jump_auto.py`,如果手机界面显示 USB 授权,请点击确认 +- 请按照你的手机分辨率从`./config/`文件夹找到相应的配置,拷贝到 *.py 同级目录`./config.json`(如果屏幕分辨率能成功探测,会直接调用 config 目录的配置,不需要复制) -## IOS手机操作步骤 +## iOS 手机操作步骤 1. 运行安装好的 `WebDriverAgentRunner` 2. 将手机点击到《跳一跳》小程序界面 -3. `python3 wechat_jump_iOS_py3.py` -4. 依次点击起始位置和目标位置,实现蓄力一跳 -5. 打开 `python3 wechat_jump_iOS_py3.py`,根据蓄力一跳的精准情况更改其中的 `time_coefficient`,直到获得最佳取值 - +3. 运行脚本。有两种模式可供选择:手动辅助跳 和 自动连续跳 + * 手动辅助跳 + * 命令行运行`python3 wechat_jump_iOS_py3.py` + * 依次点击弹出的窗口中的起始位置和目标位置,会自动计算距离后起跳 + * 根据起跳的精准情况更改`python3 wechat_jump_iOS_py3.py`中的`time_coefficient`参数,直到获得最佳取值 + * 自动连续跳 + * 拷贝`./config/iPhone`目录下对应的设备配置文件,重命名并替换到`./config.json` + * 命令行运行`python3 wechat_jump_auto_iOS.py` + * 会自动计算坐标并连续起跳,根据起跳的精准情况更改`./config.json` 中的`press_coefficient`参数,直到获得最佳取值 ## 实验结果 @@ -111,3 +144,4 @@ - [x] 可以对拉上来的图片进行颜色分割,识别小人和目标中心,这样就不需要手动点击自动弹跳。 > 事实证明,机器人比人更会玩儿游戏。 + diff --git a/config/1920x1080/config.json b/config/1920x1080/config.json index 058f4aac..a0e41ac6 100644 --- a/config/1920x1080/config.json +++ b/config/1920x1080/config.json @@ -4,9 +4,9 @@ "piece_base_height_1_2": 20, "piece_body_width": 70, "swipe" : { - "x1": 320, - "y1": 410, - "x2": 320, - "y2": 410 + "x1": 500, + "y1": 1600, + "x2": 500, + "y2": 1602 } } diff --git a/config/iPhone/6_config.json b/config/iPhone/6_config.json new file mode 100644 index 00000000..fc913ae2 --- /dev/null +++ b/config/iPhone/6_config.json @@ -0,0 +1,13 @@ +{ + "under_game_score_y": 200, + "press_coefficient": 2.0, + "piece_base_height_1_2": 13, + "piece_body_width": 49, + + "swipe" : { + "x1": 375, + "y1": 1055, + "x2": 375, + "y2": 1055 + } +} diff --git a/config/iPhone/8P_7P_6sP_6P_config.json b/config/iPhone/8P_7P_6sP_6P_config.json new file mode 100644 index 00000000..2bd81604 --- /dev/null +++ b/config/iPhone/8P_7P_6sP_6P_config.json @@ -0,0 +1,12 @@ +{ + "under_game_score_y": 300, + "press_coefficient": 1.2, + "piece_base_height_1_2": 20, + "piece_body_width": 70, + "swipe" : { + "x1": 320, + "y1": 410, + "x2": 320, + "y2": 410 + } +} diff --git a/config/samsung/s8.json b/config/samsung/s8.json new file mode 100644 index 00000000..8907b1b0 --- /dev/null +++ b/config/samsung/s8.json @@ -0,0 +1,6 @@ +{ + "under_game_score_y": 460, + "press_coefficient": 1.365, + "piece_base_height_1_2": 70, + "piece_body_width": 75 +} diff --git "a/config/samsung/s8\345\234\250\350\256\276\347\275\256\351\207\214\345\205\263\351\227\255\346\233\262\351\235\242\344\276\247\345\261\217" "b/config/samsung/s8\345\234\250\350\256\276\347\275\256\351\207\214\345\205\263\351\227\255\346\233\262\351\235\242\344\276\247\345\261\217" new file mode 100644 index 00000000..e69de29b diff --git a/requirements.txt b/requirements.txt index 16de5f12..3b9f9138 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/wechat_jump_auto.py b/wechat_jump_auto.py index 177720cc..bbea2967 100644 --- a/wechat_jump_auto.py +++ b/wechat_jump_auto.py @@ -70,7 +70,7 @@ def _get_screen_size(): swipe['x1'], swipe['y1'], swipe['x2'], swipe['y2'] = 320, 410, 320, 410 -screenshot_way = 0 +screenshot_way = 2 screenshot_backup_dir = 'screenshot_backups/' if not os.path.isdir(screenshot_backup_dir): os.mkdir(screenshot_backup_dir) @@ -79,15 +79,18 @@ def _get_screen_size(): def pull_screenshot(): global screenshot_way if screenshot_way == 0: + os.system('adb shell screencap -p /sdcard/autojump.png') + os.system('adb pull /sdcard/autojump.png .') + elif screenshot_way == 1 or screenshot_way == 2: process = subprocess.Popen('adb shell screencap -p', shell=True, stdout=subprocess.PIPE) screenshot = process.stdout.read() - binary_screenshot = screenshot.replace(b'\r\n', b'\n') + if screenshot_way == 1: + binary_screenshot = screenshot.replace(b'\r\r\n', b'\n') + else: + binary_screenshot = screenshot.replace(b'\r\n', b'\n') f = open('autojump.png', 'wb') f.write(binary_screenshot) f.close() - elif screenshot_way == 1: - os.system('adb shell screencap -p /sdcard/autojump.png') - os.system('adb pull /sdcard/autojump.png .') def backup_screenshot(ts): # 为了方便失败的时候 debug @@ -133,6 +136,49 @@ def jump(distance): print(cmd) os.system(cmd) +# 转换色彩模式hsv2rgb +def hsv2rgb(h, s, v): + h = float(h) + s = float(s) + v = float(v) + h60 = h / 60.0 + h60f = math.floor(h60) + hi = int(h60f) % 6 + f = h60 - h60f + p = v * (1 - s) + q = v * (1 - f * s) + t = v * (1 - (1 - f) * s) + r, g, b = 0, 0, 0 + if hi == 0: r, g, b = v, t, p + elif hi == 1: r, g, b = q, v, p + elif hi == 2: r, g, b = p, v, t + elif hi == 3: r, g, b = p, q, v + elif hi == 4: r, g, b = t, p, v + elif hi == 5: r, g, b = v, p, q + r, g, b = int(r * 255), int(g * 255), int(b * 255) + return r, g, b + +# 转换色彩模式rgb2hsv +def rgb2hsv(r, g, b): + r, g, b = r/255.0, g/255.0, b/255.0 + mx = max(r, g, b) + mn = min(r, g, b) + df = mx-mn + if mx == mn: + h = 0 + elif mx == r: + h = (60 * ((g-b)/df) + 360) % 360 + elif mx == g: + h = (60 * ((b-r)/df) + 120) % 360 + elif mx == b: + h = (60 * ((r-g)/df) + 240) % 360 + if mx == 0: + s = 0 + else: + s = df/mx + v = mx + return h, s, v + def find_piece_and_board(im): w, h = im.size @@ -142,6 +188,15 @@ def find_piece_and_board(im): piece_y_max = 0 board_x = 0 board_y = 0 + + left_value = 0 + left_count = 0 + right_value = 0 + right_count = 0 + from_left_find_board_y = 0 + from_right_find_board_y = 0 + + scan_x_border = int(w / 8) # 扫描棋子时的左右边界 scan_start_y = 0 # 扫描的起始y坐标 im_pixel=im.load() @@ -174,44 +229,107 @@ def find_piece_and_board(im): piece_y = piece_y_max - piece_base_height_1_2 # 上移棋子底盘高度的一半 for i in range(int(h / 3), int(h * 2 / 3)): + last_pixel = im_pixel[0, i] - if board_x or board_y: + # 计算阴影的RGB值,通过photoshop观察,阴影部分其实就是背景色的明度V 乘以0.7的样子 + h, s, v = rgb2hsv(last_pixel[0], last_pixel[1], last_pixel[2]) + r, g, b = hsv2rgb(h, s, v * 0.7) + + if from_left_find_board_y and from_right_find_board_y: break - board_x_sum = 0 - board_x_c = 0 - for j in range(w): - pixel = im_pixel[j,i] - # 修掉脑袋比下一个小格子还高的情况的 bug - if abs(j - piece_x) < piece_body_width: - continue - - # 修掉圆顶的时候一条线导致的小 bug,这个颜色判断应该 OK,暂时不提出来 - if abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) > 10: - board_x_sum += j - board_x_c += 1 - if board_x_sum: - board_x = board_x_sum / board_x_c - # 按实际的角度来算,找到接近下一个 board 中心的坐标 这里的角度应该是30°,值应该是tan 30°, math.sqrt(3) / 3 + if not board_x: + board_x_sum = 0 + board_x_c = 0 + + for j in range(w): + pixel = im_pixel[j,i] + # 修掉脑袋比下一个小格子还高的情况的 bug + if abs(j - piece_x) < piece_body_width: + continue + + # 修掉圆顶的时候一条线导致的小 bug,这个颜色判断应该 OK,暂时不提出来 + if abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) > 10: + board_x_sum += j + board_x_c += 1 + if board_x_sum: + board_x = board_x_sum / board_x_c + else: + # 继续往下查找,从左到右扫描,找到第一个与背景颜色不同的像素点,记录位置 + # 当有连续3个相同的记录时,表示发现了一条直线 + # 这条直线即为目标board的左边缘 + # 然后当前的 y 值减 3 获得左边缘的第一个像素 + # 就是顶部的左边顶点 + for j in range(w): + pixel = im_pixel[j, i] + # 修掉脑袋比下一个小格子还高的情况的 bug + if abs(j - piece_x) < piece_body_width: + continue + if (abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) + > 10) and (abs(pixel[0] - r) + abs(pixel[1] - g) + abs(pixel[2] - b) > 10): + if left_value == j: + left_count = left_count+1 + else: + left_value = j + left_count = 1 + + if left_count > 3: + from_left_find_board_y = i - 3 + break + # 逻辑跟上面类似,但是方向从右向左 + # 当有遮挡时,只会有一边有遮挡 + # 算出来两个必然有一个是对的 + for j in range(w)[::-1]: + pixel = im_pixel[j, i] + # 修掉脑袋比下一个小格子还高的情况的 bug + if abs(j - piece_x) < piece_body_width: + continue + if (abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) + > 10) and (abs(pixel[0] - r) + abs(pixel[1] - g) + abs(pixel[2] - b) > 10): + if right_value == j: + right_count = left_count + 1 + else: + right_value = j + right_count = 1 + + if right_count > 3: + from_right_find_board_y = i - 3 + break + + # 如果顶部像素比较多,说明图案近圆形,相应的求出来的值需要增大,这里暂定增大顶部宽的三分之一 + if board_x_c > 5: + from_left_find_board_y = from_left_find_board_y + board_x_c / 3 + from_right_find_board_y = from_right_find_board_y + board_x_c / 3 + + # 按实际的角度来算,找到接近下一个 board 中心的坐标 这里的角度应该是30°,值应该是tan 30°,math.sqrt(3) / 3 board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3 + # 从左从右取出两个数据进行对比,选出来更接近原来老算法的那个值 + if abs(board_y - from_left_find_board_y) > abs(from_right_find_board_y): + new_board_y = from_right_find_board_y + else: + new_board_y = from_left_find_board_y + if not all((board_x, board_y)): return 0, 0, 0, 0 - return piece_x, piece_y, board_x, board_y + return piece_x, piece_y, board_x, new_board_y def dump_device_info(): size_str = os.popen('adb shell wm size').read() device_str = os.popen('adb shell getprop ro.product.model').read() density_str = os.popen('adb shell wm density').read() - print("如果你的脚本无法工作,上报issue时请copy如下信息:\n=====\ - \nScreen: {size}\nDensity: {dpi}\nDeviceType: {type}\n=====".format( + print("如果你的脚本无法工作,上报issue时请copy如下信息:\n**********\ + \nScreen: {size}\nDensity: {dpi}\nDeviceType: {type}\nOS: {os}\nPython: {python}\n**********".format( size=size_str.strip(), type=device_str.strip(), - dpi=density_str.strip() + dpi=density_str.strip(), + os=sys.platform, + python=sys.version )) + def check_adb(): flag = os.system('adb devices') if flag == 1: @@ -222,17 +340,24 @@ def check_screenshot(): global screenshot_way if os.path.isfile('autojump.png'): os.remove('autojump.png') - if (screenshot_way >= 2): + if (screenshot_way < 0): print('暂不支持当前设备') sys.exit() pull_screenshot() try: - im = Image.open('./autojump.png') + Image.open('./autojump.png') + print('采用方式{}获取截图'.format(screenshot_way)) except: - screenshot_way += 1 + screenshot_way -= 1 check_screenshot() def main(): + + h, s, v = rgb2hsv(201, 204, 214) + print(h, s, v) + r, g, b = hsv2rgb(h, s, v*0.7) + print(r, g, b) + dump_device_info() check_adb() check_screenshot() @@ -247,7 +372,7 @@ def main(): jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2)) save_debug_creenshot(ts, im, piece_x, piece_y, board_x, board_y) backup_screenshot(ts) - time.sleep(random.uniform(1, 1.1)) # 为了保证截图的时候应落稳了,多延迟一会儿 + time.sleep(random.uniform(1.2, 1.4)) # 为了保证截图的时候应落稳了,多延迟一会儿 if __name__ == '__main__': diff --git a/wechat_jump_auto_iOS.py b/wechat_jump_auto_iOS.py new file mode 100644 index 00000000..055ea6d6 --- /dev/null +++ b/wechat_jump_auto_iOS.py @@ -0,0 +1,199 @@ +# coding: utf-8 +import os +import shutil +import time +import math +import wda +from PIL import Image, ImageDraw +import random +import json + + +# === 思路 === +# 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标, +# 根据两个点的距离乘以一个时间系数获得长按的时间 +# 识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条直线,就从上往下一行一行遍历, +# 比较颜色(颜色用了一个区间来比较)找到最下面的那一行的所有点,然后求个中点, +# 求好之后再让 Y 轴坐标减小棋子底盘的一半高度从而得到中心点的坐标 +# 识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,由于圆形的块最顶上是一条线, +# 方形的上面大概是一个点,所以就用类似识别棋子的做法多识别了几个点求中点, +# 这时候得到了块中点的 X 轴坐标,这时候假设现在棋子在当前块的中心, +# 根据一个通过截图获取的固定的角度来推出中点的 Y 坐标 +# 最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离) + + +# TODO: 解决定位偏移的问题 +# TODO: 看看两个块中心到中轴距离是否相同,如果是的话靠这个来判断一下当前超前还是落后,便于矫正 +# TODO: 一些固定值根据截图的具体大小计算 +# TODO: 直接用 X 轴距离简化逻辑 + +with open('config.json', 'r') as f: + config = json.load(f) + + +# Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需设置 +under_game_score_y = config['under_game_score_y'] # 截图中刚好低于分数显示区域的 Y 坐标,300 是 1920x1080 的值,2K 屏、全面屏请根据实际情况修改 +press_coefficient = config['press_coefficient'] # 长按的时间系数,请自己根据实际情况调节 +piece_base_height_1_2 = config['piece_base_height_1_2'] # 二分之一的棋子底座高度,可能要调节 +piece_body_width = config['piece_body_width'] # 棋子的宽度,比截图中量到的稍微大一点比较安全,可能要调节 +time_coefficient = config['press_coefficient'] + +# 模拟按压的起始点坐标,需要自动重复游戏请设置成“再来一局”的坐标 +if config.get('swipe'): + swipe = config['swipe'] +else: + swipe = { + "x1": 320, + "y1": 410, + "x2": 320, + "y2": 410 + } + +c = wda.Client() +s = c.session() + +screenshot_backup_dir = 'screenshot_backups/' +if not os.path.isdir(screenshot_backup_dir): + os.mkdir(screenshot_backup_dir) + + +def pull_screenshot(): + c.screenshot('1.png') + + +def jump(distance): + press_time = distance * time_coefficient / 1000 + print('press time: {}'.format(press_time)) + s.tap_hold(200, 200, press_time) + + +def backup_screenshot(ts): + # 为了方便失败的时候 debug + if not os.path.isdir(screenshot_backup_dir): + os.mkdir(screenshot_backup_dir) + shutil.copy('1.png', '{}{}.png'.format(screenshot_backup_dir, ts)) + + +def save_debug_creenshot(ts, im, piece_x, piece_y, board_x, board_y): + draw = ImageDraw.Draw(im) + # 对debug图片加上详细的注释 + draw.line((piece_x, piece_y) + (board_x, board_y), fill=2, width=3) + draw.line((piece_x, 0, piece_x, im.size[1]), fill=(255, 0, 0)) + draw.line((0, piece_y, im.size[0], piece_y), fill=(255, 0, 0)) + draw.line((board_x, 0, board_x, im.size[1]), fill=(0, 0, 255)) + draw.line((0, board_y, im.size[0], board_y), fill=(0, 0, 255)) + draw.ellipse((piece_x - 10, piece_y - 10, piece_x + 10, piece_y + 10), fill=(255, 0, 0)) + draw.ellipse((board_x - 10, board_y - 10, board_x + 10, board_y + 10), fill=(0, 0, 255)) + del draw + im.save('{}{}_d.png'.format(screenshot_backup_dir, ts)) + + +def set_button_position(im): + # 将swipe设置为 `再来一局` 按钮的位置 + global swipe_x1, swipe_y1, swipe_x2, swipe_y2 + w, h = im.size + left = w / 2 + top = 1003 * (h / 1280.0) + 10 + swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, left, top + + +def find_piece_and_board(im): + w, h = im.size + + print("size: {}, {}".format(w, h)) + + piece_x_sum = 0 + piece_x_c = 0 + piece_y_max = 0 + board_x = 0 + board_y = 0 + scan_x_border = int(w / 8) # 扫描棋子时的左右边界 + scan_start_y = 0 # 扫描的起始y坐标 + im_pixel = im.load() + + # 以50px步长,尝试探测scan_start_y + for i in range(under_game_score_y, h, 50): + last_pixel = im_pixel[0, i] + for j in range(1, w): + pixel = im_pixel[j, i] + + # 不是纯色的线,则记录scan_start_y的值,准备跳出循环 + if pixel[0] != last_pixel[0] or pixel[1] != last_pixel[1] or pixel[2] != last_pixel[2]: + scan_start_y = i - 50 + break + + if scan_start_y: + break + + print("scan_start_y: ", scan_start_y) + + # 从scan_start_y开始往下扫描,棋子应位于屏幕上半部分,这里暂定不超过2/3 + for i in range(scan_start_y, int(h * 2 / 3)): + for j in range(scan_x_border, w - scan_x_border): # 横坐标方面也减少了一部分扫描开销 + pixel = im_pixel[j, i] + # 根据棋子的最低行的颜色判断,找最后一行那些点的平均值,这个颜色这样应该 OK,暂时不提出来 + if (50 < pixel[0] < 60) and (53 < pixel[1] < 63) and (95 < pixel[2] < 110): + piece_x_sum += j + piece_x_c += 1 + piece_y_max = max(i, piece_y_max) + + if not all((piece_x_sum, piece_x_c)): + return 0, 0, 0, 0 + piece_x = piece_x_sum / piece_x_c + piece_y = piece_y_max - piece_base_height_1_2 # 上移棋子底盘高度的一半 + + for i in range (int (h / 3), int (h * 2 / 3)): + last_pixel = im_pixel[0, i] + if board_x or board_y: + break + board_x_sum = 0 + board_x_c = 0 + + for j in range(w): + pixel = im_pixel[j, i] + # 修掉脑袋比下一个小格子还高的情况的 bug + if abs(j - piece_x) < piece_body_width: + continue + + # 修掉圆顶的时候一条线导致的小 bug,这个颜色判断应该 OK,暂时不提出来 + if abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) > 10: + board_x_sum += j + board_x_c += 1 + + if board_x_sum: + board_x = board_x_sum / board_x_c + + # 按实际的角度来算,找到接近下一个 board 中心的坐标 这里的角度应该是30°,值应该是tan 30°, math.sqrt(3) / 3 + board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3 + + if not all((board_x, board_y)): + return 0, 0, 0, 0 + + return piece_x, piece_y, board_x, board_y + + +def main(): + while True: + pull_screenshot() + im = Image.open("./1.png") + + # 获取棋子和 board 的位置 + piece_x, piece_y, board_x, board_y = find_piece_and_board(im) + ts = int(time.time()) + print(ts, piece_x, piece_y, board_x, board_y) + if piece_x == 0: + return + + set_button_position(im) + + distance = math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2) + jump(distance) + + save_debug_creenshot(ts, im, piece_x, piece_y, board_x, board_y) + backup_screenshot(ts) + + time.sleep(random.uniform(1, 1.1)) # 为了保证截图的时候应落稳了,多延迟一会儿 + + +if __name__ == '__main__': + main() diff --git a/wechat_jump_iOS_py3.py b/wechat_jump_iOS_py3.py index 2a3ef4ab..eb9c4eda 100644 --- a/wechat_jump_iOS_py3.py +++ b/wechat_jump_iOS_py3.py @@ -8,8 +8,13 @@ import os # 截图距离 * time_coefficient = 按键时长 -# 此数据是 iPhoneX 的推荐系数,可根据手机型号进行调整 -time_coefficient = 0.00125 +# iphonex +# time_coefficient = 0.00125 +#iphone6 +# time_coefficient = 0.00196 +#iphone6s plus +time_coefficient = 0.00120 + c = wda.Client() s = c.session() @@ -20,7 +25,7 @@ def pull_screenshot(): def jump(distance): press_time = distance * time_coefficient press_time = press_time - print(press_time) + print('press_time = ',press_time) s.tap_hold(200,200,press_time) fig = plt.figure() @@ -77,5 +82,3 @@ def onClick(event): fig.canvas.mpl_connect('button_press_event', onClick) ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True) plt.show() - -