Repository: wangshub/wechat_jump_game
Branch: master
Commit: 3cbbb0795790
Files: 66
Total size: 99.1 KB
Directory structure:
gitextract_rovqql80/
├── .github/
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── README.rst
├── Tools/
│ └── README.md
├── changelog.md
├── common/
│ ├── UnicodeStreamFilter.py
│ ├── __init__.py
│ ├── ai.py
│ ├── auto_adb.py
│ ├── config.py
│ ├── debug.py
│ └── screenshot.py
├── config/
│ ├── 1280x720/
│ │ └── config.json
│ ├── 1440x720/
│ │ └── config.json
│ ├── 1800x1080/
│ │ └── config.json
│ ├── 1920x1080/
│ │ └── config.json
│ ├── 2160x1080/
│ │ └── config.json
│ ├── 2560x1440/
│ │ └── config.json
│ ├── 720x1280
│ ├── 960x540/
│ │ └── config.json
│ ├── default.json
│ ├── huawei/
│ │ ├── honorV8
│ │ └── honor_note8_config.json
│ ├── iPhone/
│ │ ├── 6_config.json
│ │ ├── 8P_7P_6sP_6P_config.json
│ │ ├── 8_config.json
│ │ ├── SE_config.json
│ │ └── X_config.json
│ ├── mi/
│ │ ├── max2_config.json
│ │ ├── mi5_config.json
│ │ ├── mi5s_config.json
│ │ ├── mi5x_config.json
│ │ ├── mi6_config.json
│ │ ├── mix2_config.json
│ │ └── note2_config.json
│ ├── samsung/
│ │ ├── s7edge_config.json
│ │ ├── s8.json
│ │ └── s8在设置里关闭曲面侧屏
│ └── smartisan/
│ └── pro2_config.json
├── jump_bot/
│ ├── Makefile
│ ├── README.rst
│ ├── jumpbot/
│ │ ├── algos.py
│ │ ├── auto.py
│ │ ├── bot.py
│ │ ├── connector.py
│ │ ├── manual.py
│ │ └── settings.py
│ └── requirements.txt
├── requirements.txt
├── resource/
│ └── model/
│ ├── checkpoint
│ ├── model.ckpt.data-00000-of-00001
│ ├── model.ckpt.index
│ └── model.ckpt.meta
├── wechat_jump.py
├── wechat_jump_auto.py
├── wechat_jump_auto_ai.py
├── wechat_jump_auto_curves.py
├── wechat_jump_auto_iOS.py
├── wechat_jump_auto_slim.py
├── wechat_jump_iOS_py3.py
└── wechat_jump_py3.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CONTRIBUTING.md
================================================
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--
感谢提交问题,提交 issue 前请先通过关键字搜索已经存在或解决了的 issue,避免重复提交相同内容。
请说清楚自己遇到了什么问题,不然可能被 close。
还没有自己机型配置文件的请自己去创建一个,好用的话欢迎 PR。
### 如果代码不能启动,执行的时候直接报错
请贴出:
1. 代码版本号,代码中的 VERSION 变量
2. 使用环境(操作系统、Python 版本)
3. 尽量完整的代码执行命令,和打印的错误信息
### 如果代码能运行,但是达不到想要的效果
请贴出:
1. 代码版本号,代码中的 VERSION 变量
2. 使用环境 (运行脚本中已包含自动识别功能,复制即可)
3. 遇到问题(如有可能,请详细描述,建议附上截图。不负责的 issue 可能会被 close)
具体实例如下,按照该例进行提交 issue
-->
程序版本号:x.x.x
**********
Screen: Physical size: x
Density: Physical density: x
Override density: x
Device: x
Phone OS: x
Host OS: x
Python: x
**********
遇到问题:
改进和建议:无
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
感谢您的 pull request!
## 在 PR 前请尽量做到:
- PR 应基于最新的 dev 分支
```
git remote add wangshub https://github.com/wangshub/wechat_jump_game.git
git fetch
git rebase wangshub/master
```
- 分支名是有意义的名称,如 add-config-file-for-mi5s 而不是 patch-1
- Base 选择 master 分支
- 尽量遵守 PEP8 规范
- 更新脚本中的 VERSION 字段
- 请描述一下 PR 做的事情,更新算法或配置文件请附上最高分数
- 请明确提交类型,为 PR 标题添加前缀:[类型](类型可填写文档,配置,优化,修复等)
-->
本次 PR 主要做的事情:
- x
修改后最高分数:x
================================================
FILE: .gitignore
================================================
# user-defined
test/
pic/
screenshot_backups/
autojump.png
autojump_temp.png
region*.png
jump_range.csv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
.idea
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
.static_storage/
.media/
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# for mac
.DS_store
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [wangshub] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
run:
mkdir -p jumpbot/data
python3 jumpbot/bot.py
================================================
FILE: README.md
================================================
# 教你用 Python 来玩微信跳一跳
[](https://github.com/wangshub/wechat_jump_game/stargazers) [](https://github.com/wangshub/wechat_jump_game/network) [](https://github.com/wangshub/wechat_jump_game/blob/master/LICENSE)
[](https://waffle.io/wangshub/wechat_jump_game/metrics/throughput)
## 游戏模式
> 2017 年 12 月 28 日下午,微信发布了 6.6.1 版本,加入了「小游戏」功能,并提供了官方 DEMO「跳一跳」。这是一个 2.5D 插画风格的益智游戏,玩家可以通过按压屏幕时间的长短来控制这个「小人」跳跃的距离。分数越高,那么在好友排行榜更加靠前。通过 Python 脚本自动运行,让你轻松霸榜。

可能刚开始上手的时候,因为时间距离之间的关系把握不恰当,只能跳出几个就掉到了台子下面。**如果能利用图像识别精确测量出起始和目标点之间测距离,就可以估计按压的时间来精确跳跃。**
## 原理说明
##### 由于微信检测非常严厉,这里的防禁代码可能已经不起作用,主要供学习用途
1. 将手机点击到《跳一跳》小程序界面
2. 用 ADB 工具获取当前手机截图,并用 ADB 将截图 pull 上来
```shell
adb shell screencap -p /sdcard/autojump.png
adb pull /sdcard/autojump.png .
```
3. 计算按压时间
* 手动版:用 Matplotlib 显示截图,用鼠标先点击起始点位置,然后点击目标位置,计算像素距离;
* 自动版:靠棋子的颜色来识别棋子,靠底色和方块的色差来识别棋盘;
4. 用 ADB 工具点击屏幕蓄力一跳
```shell
adb shell input swipe x y x y time(ms)
```
## 使用教程
相关软件工具安装和使用步骤请参考 [Android 和 iOS 操作步骤](https://github.com/wangshub/wechat_jump_game/wiki/Android-%E5%92%8C-iOS-%E6%93%8D%E4%BD%9C%E6%AD%A5%E9%AA%A4)
#### 获取源码
```
- git clone https://github.com/wangshub/wechat_jump_game.git
```
##### 非常推荐使用Python3,避免编码及import问题
## PR 要求
##### 请选择 merge 进 master 分支,并且标题写上简短描述,例子
[优化] 使用PEP8优化代码
## 版本说明
- master 分支:稳定版本,已通过测试
- dev 分支:开发版本,包含一些较稳定的新功能,累计多个功能并测试通过后合并至 prod 分支
- 其他分支:功能开发 (feature) 或问题修复 (bugfix),属于最新尝鲜版本,可能处于开发中的状态,基本完成后合并至 dev 分支
## FAQ
- 详见 [Wiki-FAQ](https://github.com/wangshub/wechat_jump_game/wiki/FAQ)
## 更新日志
- 详见 [changelog](https://github.com/wangshub/wechat_jump_game/blob/master/changelog.md)
## 开发者列表
- 详见 [contributors](https://github.com/wangshub/wechat_jump_game/graphs/contributors)
## 交流
- 314659953 (1000 人)
- 176740763 (500 人)
- 或者关注我的微信公众号后台留言

================================================
FILE: README.rst
================================================
##############################################################################
Wechat Jump Bot (iOS)
##############################################################################
==============================================================================
Features
==============================================================================
- Auto Mode: play the game automatically;
- Manual Mode: play the game manually.
Wechat Jump Game
.. image:: https://github.com/alpesis-ai/wechat-jumpbot/blob/master/images/auto.png
:height: 1334px
:width: 500px
==============================================================================
How it runs
==============================================================================
Prerequisites
- WebDriverAgent
- libimobiledevice
- Python 3
WebDriverAgent
::
$ git clone https://github.com/facebook/WebDriverAgent && cd WebDriverAgent
$ brew install carthage
$ ./Scripts/bootstrap.sh
# open WebDriverAgent.xcodeproj with Xcode
# Xcode:
# - code sign (general and build_settings): WebDriverAgentLib/WebDriverAgentRunner
# - Product -> Destination -> <your device>
# - Product -> Scheme -> WebDriverAgentRunner
# - Product -> Test
libimobiledevice (iproxy)
::
$ brew install libimobiledevice
$ iproxy 8100 8100
# browse: http://localhost:8100/status
# browse: http://localhost:8100/inspector
Bot Agent (iOS)
::
$ git clone https://github.com/alpesis-ai/wechat-jumpbot.git
$ cd bot-agent-ios
$ pip3 install --pre facebook-wda
$ pip3 install -r requirements.txt
# make run
# - model: [ip, plus, ipx, se]
# - mode: [auto, manual]
# python3 jumpbot/bot.py --model <device_model> --mode <mode>
$ mkdir -p jumpbot/data
# iphone 6/7
$ python3 jumpbot/bot.py --model ip --mode auto
# iphone 6/7 plus
$ python3 jumpbot/bot.py --model plus --mode auto
# iphone X
$ python3 jumpbot/bot.py --model ipx --mode auto
# iphone SE
$ python3 jumpbot/bot.py --model se --mode auto
==============================================================================
Algorithms
==============================================================================
Manual Mode:
- click the piece(x, y) and board(x, y) and get the coordinates correspondingly
- calculating the distance and press time
::
(coord1[0][0] - coord2[0][0])**2 + (coord2[0][1] - coord2[0][1])**2
distance = distance ** 0.5
press_time = distance * settings.TIME_COEFF
Auto Mode:
- the main idea same as the manual mode, but detecting the piece and the board automatically
- find coord_y_start_scan
- find piece
- find board
==============================================================================
Developement
==============================================================================
::
connector ---|
| --> auto / manual --> bot
algos ---|
================================================
FILE: Tools/README.md
================================================
## 所有实验都在该文件夹下运行即可,已放上测试用的代码。免去配置 adb 的麻烦
- 复制 wechat_jump_game 根目录下的 config 文件夹以及 wechat_jump_py3.py 文件到本目录下
- 按住 `shift` + 右键 选择在该文件夹下打开命令窗口(Windows 用户请自行 cmd)
- 打开安卓手机的 usb 调试,并连接电脑,在终端输入 `adb devices` 进行测试,如果有连接设备号则表示成功
- 打开微信小游戏,然后运行代码 `python wechat_jump_py3.py`,点击出现的图形起点和终点,棋子自动跳转
**注意:这里使用的是不需要配置的 adb 方式,需要在该文件下操作,至于如何自动跳转,只需改变执行脚本即可,这里只做演示**
================================================
FILE: changelog.md
================================================
## 更新日志
- 2018-1-8
- 发布 release 一键启动 app V1.1.2 版本,针对腾讯 ban 方法进行反 ban 操作,详见 [STOP_jump](https://github.com/wangshub/wechat_jump_game/releases)
- 2018-1-3 :
- 发布 release 一键启动 app,详见 [STOP_jump](https://github.com/wangshub/wechat_jump_game/releases)
- 2017-12-30 :
- 请将安卓手机的 USB 调试模式打开,设置 > 更多设置 > 开发者选项 > USB 调试,如果出现运行脚本后小人不跳的情况,请检查是否有打开 “USB 调试(安全模式)”
- 根据大家反馈:1080 屏幕距离系数 **1.393**,2K 屏幕为 **1**
- 添加部分机型配置文件,可直接复制使用
- 2017-12-29 :
- 增加更新自动化运行脚本,感谢 GitHub 上的 [@binderclip](https://github.com/binderclip)
================================================
FILE: common/UnicodeStreamFilter.py
================================================
# -*- coding: utf-8 -*-
import sys
if sys.version_info.major != 3:
class UnicodeStreamFilter:
def __init__(self, target):
self.target = target
self.encoding = 'utf-8'
self.errors = 'replace'
self.encode_to = self.target.encoding
def write(self, s):
if type(s) == str:
s = s.decode("utf-8")
s = s.encode(self.encode_to, self.errors).decode(self.encode_to)
self.target.write(s)
if sys.stdout.encoding == 'cp936':
sys.stdout = UnicodeStreamFilter(sys.stdout)
else:
pass
================================================
FILE: common/__init__.py
================================================
================================================
FILE: common/ai.py
================================================
# coding: utf-8
# Copyright (c) 2018 BeiTown
import os
import pandas
from sklearn.linear_model import LinearRegression
def linear_model_main(_distances, _press_times, target_distance):
regr = LinearRegression()
regr.fit(_distances, _press_times)
predict_press_time = regr.predict(target_distance)
result = {}
# 截距 b
result['intercept'] = regr.intercept_
# 斜率值 k
result['coefficient'] = regr.coef_
# 预估的按压时间
result['value'] = predict_press_time
return result
def computing_k_b_v(target_distance):
result = linear_model_main(distances, press_times, target_distance)
b = result['intercept']
k = result['coefficient']
v = result['value']
return k[0], b[0], v[0]
def add_data(distance, press_time):
distances.append([distance])
press_times.append([press_time])
save_data('./jump_range.csv', distances, press_times)
def save_data(file_name, distances, press_times):
pf = pandas.DataFrame({'Distance': distances, 'Press_time': press_times})
# print(pf)
pf.to_csv(file_name, index=False, sep=',')
def get_data(file_name):
data = pandas.read_csv(file_name)
distance_array = []
press_time_array = []
for distance, press_time in zip(data['Distance'], data['Press_time']):
distance_array.append([float(distance.strip().strip('[]'))])
press_time_array.append([float(press_time.strip().strip('[]'))])
return distance_array, press_time_array
def init():
global distances, press_times
distances = []
press_times = []
if os.path.exists('./jump_range.csv'):
distances, press_times = get_data('./jump_range.csv')
else:
save_data('./jump_range.csv', [], [])
return 0
def get_result_len():
return len(distances)
================================================
FILE: common/auto_adb.py
================================================
# -*- coding: utf-8 -*-
import os
import subprocess
import platform
class auto_adb():
def __init__(self):
try:
adb_path = 'adb'
subprocess.Popen([adb_path], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
self.adb_path = adb_path
except OSError:
if platform.system() == 'Windows':
adb_path = os.path.join('Tools', "adb", 'adb.exe')
try:
subprocess.Popen(
[adb_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.adb_path = adb_path
except OSError:
pass
else:
try:
subprocess.Popen(
[adb_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError:
pass
print('请安装 ADB 及驱动并配置环境变量')
print('具体链接: https://github.com/wangshub/wechat_jump_game/wiki')
exit(1)
def get_screen(self):
process = os.popen(self.adb_path + ' shell wm size')
output = process.read()
return output
def run(self, raw_command):
command = '{} {}'.format(self.adb_path, raw_command)
process = os.popen(command)
output = process.read()
return output
def test_device(self):
print('检查设备是否连接...')
command_list = [self.adb_path, 'devices']
process = subprocess.Popen(command_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = process.communicate()
if output[0].decode('utf8') == 'List of devices attached\n\n':
print('未找到设备')
print('adb 输出:')
for each in output:
print(each.decode('utf8'))
exit(1)
print('设备已连接')
print('adb 输出:')
for each in output:
print(each.decode('utf8'))
def test_density(self):
process = os.popen(self.adb_path + ' shell wm density')
output = process.read()
return output
def test_device_detail(self):
process = os.popen(self.adb_path + ' shell getprop ro.product.device')
output = process.read()
return output
def test_device_os(self):
process = os.popen(self.adb_path + ' shell getprop ro.build.version.release')
output = process.read()
return output
def adb_path(self):
return self.adb_path
================================================
FILE: common/config.py
================================================
# -*- coding: utf-8 -*-
"""
调取配置文件和屏幕分辨率的代码
"""
import os
import sys
import json
import re
from common.auto_adb import auto_adb
adb = auto_adb()
def open_accordant_config():
"""
调用配置文件
"""
screen_size = _get_screen_size()
config_file = "{path}/config/{screen_size}/config.json".format(
path=sys.path[0],
screen_size=screen_size
)
# 优先获取执行文件目录的配置文件
here = sys.path[0]
for file in os.listdir(here):
if re.match(r'(.+)\.json', file):
file_name = os.path.join(here, file)
with open(file_name, 'r') as f:
print("Load config file from {}".format(file_name))
return json.load(f)
# 根据分辨率查找配置文件
if os.path.exists(config_file):
with open(config_file, 'r') as f:
print("正在从 {} 加载配置文件".format(config_file))
return json.load(f)
else:
with open('{}/config/default.json'.format(sys.path[0]), 'r') as f:
print("Load default config")
return json.load(f)
def _get_screen_size():
"""
获取手机屏幕大小
"""
size_str = adb.get_screen()
m = re.search(r'(\d+)x(\d+)', size_str)
if m:
return "{height}x{width}".format(height=m.group(2), width=m.group(1))
return "1920x1080"
================================================
FILE: common/debug.py
================================================
# -*- coding: utf-8 -*-
"""
这是debug的代码,当DEBUG_SWITCH开关开启的时候,会将各种信息存在本地,方便检查故障
"""
import os
import sys
import shutil
import math
from PIL import ImageDraw
import platform
if platform.system() == 'Windows':
os.chdir(os.getcwd().replace('\\common', ''))
path_split = "\\"
else:
os.chdir(os.getcwd().replace('/common', ''))
path_split = '/'
# from common import ai
try:
from common.auto_adb import auto_adb
except ImportError as ex:
print(ex)
print('请将脚本放在项目根目录中运行')
print('请检查项目根目录中的 common 文件夹是否存在')
exit(1)
screenshot_backup_dir = 'screenshot_backups'
adb = auto_adb()
def make_debug_dir(screenshot_backup_dir):
"""
创建备份文件夹
"""
if not os.path.isdir(screenshot_backup_dir):
os.mkdir(screenshot_backup_dir)
def backup_screenshot(ts):
"""
为了方便失败的时候 debug
"""
make_debug_dir(screenshot_backup_dir)
shutil.copy('{}{}autojump.png'.format(os.getcwd(), path_split),
os.path.join(os.getcwd(), screenshot_backup_dir,
str(ts) + '.png'))
def save_debug_screenshot(ts, im, piece_x, piece_y, board_x, board_y):
"""
对 debug 图片加上详细的注释
"""
make_debug_dir(screenshot_backup_dir)
draw = ImageDraw.Draw(im)
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(os.path.join(os.getcwd(), screenshot_backup_dir,
'#' + str(ts) + '.png'))
def computing_error(last_press_time, target_board_x, target_board_y, last_piece_x, last_piece_y, temp_piece_x,
temp_piece_y):
"""
计算跳跃实际误差
"""
target_distance = math.sqrt(
(target_board_x - last_piece_x) ** 2 + (target_board_y - last_piece_y) ** 2) # 上一轮目标跳跃距离
actual_distance = math.sqrt((temp_piece_x - last_piece_x) ** 2 + (temp_piece_y - last_piece_y) ** 2) # 上一轮实际跳跃距离
jump_error_value = math.sqrt((target_board_x - temp_piece_x) ** 2 + (target_board_y - temp_piece_y) ** 2) # 跳跃误差
print(round(target_distance), round(jump_error_value), round(actual_distance), round(last_press_time))
''''# 将结果采集进学习字典
if last_piece_x > 0 and last_press_time > 0:
ai.add_data(round(actual_distance, 2), round(last_press_time))
# print(round(actual_distance), round(last_press_time))'''
def dump_device_info():
"""
显示设备信息
"""
size_str = adb.get_screen()
device_str = adb.test_device_detail()
phone_os_str = adb.test_device_os()
density_str = adb.test_density()
print("""**********
Screen: {size}
Density: {dpi}
Device: {device}
Phone OS: {phone_os}
Host OS: {host_os}
Python: {python}
**********""".format(
size=size_str.replace('\n', ''),
dpi=density_str.replace('\n', ''),
device=device_str.replace('\n', ''),
phone_os=phone_os_str.replace('\n', ''),
host_os=sys.platform,
python=sys.version
))
================================================
FILE: common/screenshot.py
================================================
# -*- coding: utf-8 -*-
"""
手机屏幕截图的代码
"""
import subprocess
import os
import sys
from PIL import Image
from io import StringIO
try:
from common.auto_adb import auto_adb
except Exception as ex:
print(ex)
print('请将脚本放在项目根目录中运行')
print('请检查项目根目录中的 common 文件夹是否存在')
exit(1)
adb = auto_adb()
# SCREENSHOT_WAY 是截图方法,经过 check_screenshot 后,会自动递减,不需手动修改
SCREENSHOT_WAY = 3
def pull_screenshot():
"""
获取屏幕截图,目前有 0 1 2 3 四种方法,未来添加新的平台监测方法时,
可根据效率及适用性由高到低排序
"""
global SCREENSHOT_WAY
if 1 <= SCREENSHOT_WAY <= 3:
process = subprocess.Popen(
adb.adb_path + ' shell screencap -p',
shell=True, stdout=subprocess.PIPE)
binary_screenshot = process.stdout.read()
if SCREENSHOT_WAY == 2:
binary_screenshot = binary_screenshot.replace(b'\r\n', b'\n')
elif SCREENSHOT_WAY == 1:
binary_screenshot = binary_screenshot.replace(b'\r\r\n', b'\n')
return Image.open(StringIO(binary_screenshot))
elif SCREENSHOT_WAY == 0:
adb.run('shell screencap -p /sdcard/autojump.png')
adb.run('pull /sdcard/autojump.png .')
return Image.open('./autojump.png')
def check_screenshot():
"""
检查获取截图的方式
"""
global SCREENSHOT_WAY
if os.path.isfile('autojump.png'):
try:
os.remove('autojump.png')
except Exception:
pass
if SCREENSHOT_WAY < 0:
print('暂不支持当前设备')
sys.exit()
try:
im = pull_screenshot()
im.load()
im.close()
print('采用方式 {} 获取截图'.format(SCREENSHOT_WAY))
except Exception:
SCREENSHOT_WAY -= 1
check_screenshot()
================================================
FILE: config/1280x720/config.json
================================================
{
"under_game_score_y": 200,
"press_coefficient": 2.099,
"piece_base_height_1_2": 13,
"piece_body_width": 47,
"swipe": {
"x1": 374,
"y1": 1060,
"x2": 374,
"y2": 1060
}
}
================================================
FILE: config/1440x720/config.json
================================================
{
"under_game_score_y": 200,
"press_coefficient": 2.099,
"piece_base_height_1_2": 13,
"piece_body_width": 47,
"swipe" : {
"x1": 360,
"y1": 1140,
"x2": 360,
"y2": 1142
}
}
================================================
FILE: config/1800x1080/config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.2,
"piece_base_height_1_2": 20,
"piece_body_width": 70,
"swipe": {
"x1": 500,
"y1": 1600,
"x2": 500,
"y2": 1602
}
}
================================================
FILE: config/1920x1080/config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.392,
"piece_base_height_1_2": 20,
"piece_body_width": 70,
"head_diameter": 60,
"swipe": {
"x1": 500,
"y1": 1600,
"x2": 500,
"y2": 1602
}
}
================================================
FILE: config/2160x1080/config.json
================================================
{
"under_game_score_y": 420,
"press_coefficient": 1.372,
"piece_base_height_1_2": 25,
"piece_body_width": 85
}
================================================
FILE: config/2560x1440/config.json
================================================
{
"under_game_score_y": 410,
"press_coefficient": 1.475,
"piece_base_height_1_2": 28,
"piece_body_width": 110,
"swipe": {
"x1": 320,
"y1": 410,
"x2": 320,
"y2": 410
}
}
================================================
FILE: config/720x1280
================================================
{
"under_game_score_y": 369,
"press_coefficient": 2.003,
"piece_base_height_1_2": 13,
"piece_body_width": 45,
"swipe" : {
"x1": 374,
"y1": 1060,
"x2": 374,
"y2": 1060
}
}
================================================
FILE: config/960x540/config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 2.732,
"piece_base_height_1_2": 20,
"piece_body_width": 70
}
================================================
FILE: config/default.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.392,
"piece_base_height_1_2": 20,
"piece_body_width": 70
}
================================================
FILE: config/huawei/honorV8
================================================
{
"under_game_score_y": 400,
"press_coefficient": 1.07,
"piece_base_height_1_2": 90,
"piece_body_width": 120,
"swipe": {
"x1": 730,
"y1": 2100,
"x2": 720,
"y2": 2100
}
}
================================================
FILE: config/huawei/honor_note8_config.json
================================================
{
"under_game_score_y": 400,
"press_coefficient": 1.04,
"piece_base_height_1_2": 90,
"piece_body_width": 120,
"swipe": {
"x1": 730,
"y1": 2100,
"x2": 720,
"y2": 2100
}
}
================================================
FILE: config/iPhone/6_config.json
================================================
{
"under_game_score_y": 200,
"press_coefficient": 1.95,
"piece_base_height_1_2": 13,
"piece_body_width": 49,
"swipe": {
"x1": 375,
"y1": 1055,
"x2": 375,
"y2": 1055
}
}
================================================
FILE: config/iPhone/8P_7P_6sP_6P_config.json
================================================
{
"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
}
}
================================================
FILE: config/iPhone/8_config.json
================================================
{
"under_game_score_y": 200,
"press_coefficient": 1.97,
"piece_base_height_1_2": 13,
"piece_body_width": 50,
"swipe": {
"x1": 375,
"y1": 1200,
"x2": 375,
"y2": 1200
}
}
================================================
FILE: config/iPhone/SE_config.json
================================================
{
"under_game_score_y": 190,
"press_coefficient": 2.3,
"piece_base_height_1_2": 12,
"piece_body_width": 50,
"swipe" : {
"x1": 375,
"y1": 1055,
"x2": 375,
"y2": 1055
}
}
================================================
FILE: config/iPhone/X_config.json
================================================
{
"under_game_score_y": 170,
"press_coefficient": 1.31,
"piece_base_height_1_2": 23,
"piece_body_width": 70,
"swipe": {
"x1": 320,
"y1": 410,
"x2": 320,
"y2": 410
}
}
================================================
FILE: config/mi/max2_config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.5,
"piece_base_height_1_2": 20,
"piece_body_width": 70,
"swipe": {
"x1": 500,
"y1": 1600,
"x2": 500,
"y2": 1600
}
}
================================================
FILE: config/mi/mi5_config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.475,
"piece_base_height_1_2": 20,
"piece_body_width": 70,
"swipe": {
"x1": 540,
"y1": 1514,
"x2": 540,
"y2": 1514
}
}
================================================
FILE: config/mi/mi5s_config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.475,
"piece_base_height_1_2": 20,
"piece_body_width": 70,
"swipe": {
"x1": 540,
"y1": 1514,
"x2": 540,
"y2": 1514
}
}
================================================
FILE: config/mi/mi5x_config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.45,
"piece_base_height_1_2": 25,
"piece_body_width": 80,
"swipe": {
"x1": 560,
"y1": 1550,
"x2": 560,
"y2": 1550
}
}
================================================
FILE: config/mi/mi6_config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.45,
"piece_base_height_1_2": 20,
"piece_body_width": 70,
"swipe": {
"x1": 500,
"y1": 1600,
"x2": 500,
"y2": 1600
}
}
================================================
FILE: config/mi/mix2_config.json
================================================
{
"under_game_score_y": 420,
"press_coefficient": 1.480,
"piece_base_height_1_2": 25,
"piece_body_width": 85
}
================================================
FILE: config/mi/note2_config.json
================================================
{
"under_game_score_y": 300,
"press_coefficient": 1.47,
"piece_base_height_1_2": 25,
"piece_body_width": 80,
"swipe": {
"x1": 540,
"y1": 1600,
"x2": 540,
"y2": 1600
}
}
================================================
FILE: config/samsung/s7edge_config.json
================================================
{
"under_game_score_y": 384,
"press_coefficient": 1,
"piece_base_height_1_2": 95,
"piece_body_width": 102
}
================================================
FILE: config/samsung/s8.json
================================================
{
"under_game_score_y": 460,
"press_coefficient": 1.365,
"piece_base_height_1_2": 70,
"piece_body_width": 75
}
================================================
FILE: config/samsung/s8在设置里关闭曲面侧屏
================================================
================================================
FILE: config/smartisan/pro2_config.json
================================================
{
"under_game_score_y": 411,
"press_coefficient": 1.392,
"piece_base_height_1_2": 20,
"piece_body_width": 70,
"swipe" : {
"x1": 320,
"y1": 410,
"x2": 320,
"y2": 410
}
}
================================================
FILE: jump_bot/Makefile
================================================
run:
mkdir -p jumpbot/data
python3 jumpbot/bot.py
================================================
FILE: jump_bot/README.rst
================================================
##############################################################################
Wechat Jump Bot (iOS)
##############################################################################
==============================================================================
Features
==============================================================================
- Auto Mode: playing the game automatically;
- Manual Mode: playing the game by manual.
Wechat Jump Game
.. image:: https://github.com/alpesis-ai/wechat-jumpbot/blob/master/images/auto.png
:height: 1334px
:width: 500px
==============================================================================
How it runs
==============================================================================
Prequsites
- WebDriverAgent
- libimobiledevice
- Python 3
WebDriverAgent
::
$ git clone https://github.com/facebook/WebDriverAgent && cd WebDriverAgent
$ brew install carthage
$ ./Scripts/bootstrap.sh
# open WebDriverAgent.xcodeproj with Xcode
# Xcode:
# - code sign (general and build_settings): WebDriverAgentLib/WebDriverAgentRunner
# - Product -> Destination -> <your device>
# - Product -> Scheme -> WebDriverAgentRunner
# - Product -> Test
libimobiledevice (iproxy)
::
$ brew install libimobiledevice
$ iproxy 8100 8100
# browse: http://localhost:8100/status
# browse: http://localhost:8100/inspector
Bot Agent (iOS)
::
$ git clone https://github.com/alpesis-ai/wechat-jumpbot.git
$ cd bot-agent-ios
$ pip3 install --pre facebook-wda
$ pip3 install -r requirements.txt
# make run
# - model: [ip, plus, ipx, se]
# - mode: [auto, manual]
# python3 jumpbot/bot.py --model <device_model> --mode <mode>
$ mkdir -p jumpbot/data
# iphone 6/7
$ python3 jumpbot/bot.py --model ip --mode auto
# iphone 6/7 plus
$ python3 jumpbot/bot.py --model plus --mode auto
# iphone X
$ python3 jumpbot/bot.py --model ipx --mode auto
# iphone SE
$ python3 jumpbot/bot.py --model se --mode auto
==============================================================================
Algorithms
==============================================================================
Manual Mode:
- click the piece(x, y) and board(x, y) and get the coordinates correspondingly
- calculating the distance and press time
::
(coord1[0][0] - coord2[0][0])**2 + (coord2[0][1] - coord2[0][1])**2
distance = distance ** 0.5
press_time = distance * settings.TIME_COEFF
Auto Mode:
- the main idea same as the manual mode, but detecting the piece and the board automatically
- find coord_y_start_scan
- find piece
- find board
==============================================================================
Developement
==============================================================================
::
connector ---|
| --> auto / manual --> bot
algos ---|
================================================
FILE: jump_bot/jumpbot/algos.py
================================================
import math
def get_press_time(piece_x, piece_y, board_x, board_y, time_coeff):
distance = math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2)
press_time = distance * time_coeff / 1000
return press_time
================================================
FILE: jump_bot/jumpbot/auto.py
================================================
import time
import math
import random
from PIL import Image, ImageDraw
import settings
from connector import Connector
from algos import get_press_time
class AutoBot(Connector):
def __init__(self, params=settings.get_bot_params()):
# init connector
super(AutoBot, self).__init__()
# init bot
self.status = True
# init game
self.params = params
self.swipe_x1 = 0;
self.swipe_y1 = 0;
self.swipe_x2 = 0;
self.swipe_y2 = 0;
def run(self):
steps = 0
while (self.status):
self.connector_screenshot()
image = Image.open(self.image_dir)
steps += 1
coord_y_start_scan = self._get_coord_y_start_scan(image)
piece_x, piece_y = self._find_piece(image, coord_y_start_scan)
board_x, board_y = self._find_board(image, piece_x, piece_y)
print("step: ", steps)
print("- image: ", image.size)
print("- coord_y_start_scan: ", coord_y_start_scan)
print("- piece (x, y): ", piece_x, piece_y)
print("- board (x, y): ", board_x, board_y)
if piece_x == 0:
print("Game Over.")
return
self._set_button_coords(image)
press_time = get_press_time(piece_x, piece_y, board_x, board_y, self.params["TIME_COEFF"])
print("- press time: ", press_time)
self.connector_taphold(press_time)
time.sleep(random.uniform(1, 1.1))
def _get_coord_y_start_scan(self, image):
width, height = image.size
pixels = image.load()
coord_y_start_scan = 0
for i in range(self.params["COORD_Y_START_SCAN"], height, 50):
last_pixel = pixels[0, i]
for j in range(1, width):
pixel = pixels[j, i]
if (pixel[0] != last_pixel[0]) or (pixel[1] != last_pixel[1]) or (pixel[2] != last_pixel[2]):
coord_y_start_scan = i - 50
break;
if coord_y_start_scan: break;
return coord_y_start_scan
def _find_piece(self, image, coord_y_start_scan):
width, height = image.size
pixels = image.load()
border_x_scan = int(width/8)
piece_x_sum = 0
piece_x_counter = 0
piece_y_max = 0
for i in range(coord_y_start_scan, int(height * 2 / 3)):
for j in range(border_x_scan, width - border_x_scan):
pixel = pixels[j, i]
if (50 < pixel[0] < 60) and (53 < pixel[1] < 63) and (95 < pixel[2] < 110):
piece_x_sum += j
piece_x_counter += 1
piece_y_max = max(i, piece_y_max)
if not all((piece_x_sum, piece_x_counter)): return 0, 0
piece_x = piece_x_sum / piece_x_counter
piece_y = piece_y_max - self.params["PIECE_BASE_HEIGHT_HALF"]
return piece_x, piece_y
def _find_board(self, image, piece_x, piece_y):
width, height = image.size
pixels = image.load()
board_x = 0
board_y = 0
for i in range(int(height/3), int(height * 2/3)):
if board_x or board_y:
break;
board_x_sum = 0
board_x_counter = 0
last_pixel = pixels[0, i]
for j in range(width):
pixel = pixels[j, i]
if (abs(j - piece_x) < self.params["PIECE_BODY_WIDTH"]):
continue
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_counter += 1
if board_x_sum:
board_x = board_x_sum / board_x_counter
# find the centroid of next board
board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3
if not all ((board_x, board_y)):
return 0, 0
return board_x, board_y
def _set_button_coords(self, image):
width, height = image.size
left = width / 2
top = 1003 * (height / 1280.0) + 10
self.swipe_x1, self.swipe_y1, self.swipe_x2, self.swipe_y2 = left, top, left, top
================================================
FILE: jump_bot/jumpbot/bot.py
================================================
import argparse
import settings
from auto import AutoBot
from manual import ManualBot
def config():
parser = argparse.ArgumentParser(description='Wechat Jump Bot')
parser.add_argument('-ml', '--model',
type=str,
help='device model = [ip, plus, ipx, se]',
required=True)
parser.add_argument('-me', '--mode',
type=str,
help='mode = [auto, manual]',
required=True)
return parser.parse_args()
def jumpbot(parser):
if parser.mode == "manual":
bot = ManualBot(params=settings.get_bot_params(parser.model))
bot.run()
elif parser.mode == "auto":
bot = AutoBot(params=settings.get_bot_params(parser.model))
bot.run()
else:
print("ParamError: MODE should be ['auto', 'manual'].")
if __name__ == '__main__':
parser = config()
jumpbot(parser)
================================================
FILE: jump_bot/jumpbot/connector.py
================================================
import wda
import settings
class Connector:
def __init__(self, image_dir=settings.IMAGE_DIR):
self.image_dir = image_dir
self.client = wda.Client()
self.session = self.client.session()
def connector_screenshot(self):
self.client.screenshot(self.image_dir)
def connector_taphold(self, value):
self.session.tap_hold(200, 200, value)
================================================
FILE: jump_bot/jumpbot/manual.py
================================================
import time
import math
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import settings
from connector import Connector
from algos import get_press_time
class ManualBot(Connector):
def __init__(self, params=settings.get_bot_params()):
# init connector
super(ManualBot, self).__init__()
# init figure
self.figure = plt.figure()
# actions
self.steps = 0
self.params = params
self.coords = []
self.ix = [0, 0]
self.iy = [0, 0]
self.click_counter = 0
self.status = True
def run(self):
self.connector_screenshot()
self.image = plt.imshow(self._read_image(), animated=True)
self.action()
def action(self):
self.figure.canvas.mpl_connect('button_press_event', self._onclick)
ani = animation.FuncAnimation(self.figure, self._update_figure, interval=50, blit=True)
plt.show()
def _onclick(self, event):
coord = []
self.ix, self.iy = event.xdata, event.ydata
coord.append((self.ix, self.iy))
print("coordinate = ", coord)
self.coords.append(coord)
self.click_counter += 1
if self.click_counter > 1:
self.click_counter = 0
# next screen
coord1 = self.coords.pop()
coord2 = self.coords.pop()
press_time = get_press_time(coord1[0][0], coord1[0][1],
coord2[0][0], coord2[0][1],
self.params["TIME_COEFF"])
self.steps += 1
print("Step: ", self.steps)
print("- coord1: ", coord1)
print("- coord2: ", coord2)
print("- press_time: ", press_time)
self.connector_taphold(press_time)
self.status = True
def _update_figure(self, *args):
if self.status:
time.sleep(1)
self.connector_screenshot()
self.image.set_array(self._read_image())
self.status = False
return self.image,
def _read_image(self):
return np.array(Image.open(self.image_dir))
================================================
FILE: jump_bot/jumpbot/settings.py
================================================
# Wechat Jump Bot (iOS)
# ----------------------------------------------------------------------------
import os
CURRENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_ROOT = os.path.dirname(CURRENT_DIR)
PROJECT_DIR = "jumpbot/"
# ----------------------------------------------------------------------------
# Screenshot
DATA_DIR = "data/"
IMAGE = "screen.png"
IMAGE_DIR = PROJECT_DIR + DATA_DIR + IMAGE
# ----------------------------------------------------------------------------
# mode: ['auto', 'manual']
MODE = "manual"
# ----------------------------------------------------------------------------
# Params
def get_bot_params(model="ip"):
bot_params = {
"TIME_COEFF": 2.,
"COORD_Y_START_SCAN": 200,
"PIECE_BASE_HEIGHT_HALF": 13,
"PIECE_BODY_WIDTH": 49,
"SWIPE_X1": 375,
"SWIPE_Y1": 1055,
"SWIPE_X2": 375,
"SWIPE_Y2": 1055
}
if model == "ip":
bot_params["TIME_COEFF"] = 2.
bot_params["COORD_Y_START_SCAN"] = 200
bot_params["PIECE_BASE_HEIGHT_HALF"] = 13
bot_params["PIECE_BODY_WIDTH"] = 49
bot_params["SWIPE_X1"] = 375
bot_params["SWIPE_Y1"] = 1055
bot_params["SWIPE_X2"] = 375
bot_params["SWIPE_Y2"] = 1055
elif model == "plus":
bot_params["TIME_COEFF"] = 1.2
bot_params["COORD_Y_START_SCAN"] = 300
bot_params["PIECE_BASE_HEIGHT_HALF"] = 20
bot_params["PIECE_BODY_WIDTH"] = 70
bot_params["SWIPE_X1"] = 320
bot_params["SWIPE_Y1"] = 410
bot_params["SWIPE_X2"] = 320
bot_params["SWIPE_Y2"] = 410
elif model == "ipx":
bot_params["TIME_COEFF"] = 1.31
bot_params["COORD_Y_START_SCAN"] = 170
bot_params["PIECE_BASE_HEIGHT_HALF"] = 23
bot_params["PIECE_BODY_WIDTH"] = 70
bot_params["SWIPE_X1"] = 320
bot_params["SWIPE_Y1"] = 410
bot_params["SWIPE_X2"] = 320
bot_params["SWIPE_Y2"] = 410
elif model == "se":
bot_params["TIME_COEFF"] = 2.3
bot_params["COORD_Y_START_SCAN"] = 190
bot_params["PIECE_BASE_HEIGHT_HALF"] = 12
bot_params["PIECE_BODY_WIDTH"] = 50
bot_params["SWIPE_X1"] = 375
bot_params["SWIPE_Y1"] = 1055
bot_params["SWIPE_X2"] = 375
bot_params["SWIPE_Y2"] = 1055
else:
print("ParamError: Unknown model type, model should be [ip, plus, ipx, se]")
return bot_params
================================================
FILE: jump_bot/requirements.txt
================================================
backports.functools-lru-cache==1.4
cycler==0.10.0
matplotlib==2.1.1
numpy==1.13.3
olefile==0.44
opencv-python==3.4.0.12
Pillow==4.3.0
pyparsing==2.2.0
python-dateutil==2.6.1
pytz==2017.3
six==1.11.0
================================================
FILE: requirements.txt
================================================
backports.functools-lru-cache==1.4
cycler==0.10.0
matplotlib==2.1.1
numpy==1.13.3
olefile==0.44
opencv-python==3.4.0.12
Pillow==4.3.0
pyparsing==2.2.0
python-dateutil==2.6.1
pytz==2017.3
six==1.11.0
tensorflow==1.4.0
pandas==0.22.0
scipy==1.0.0
scikit_learn==0.19.1
================================================
FILE: resource/model/checkpoint
================================================
model_checkpoint_path: "model.ckpt"
all_model_checkpoint_paths: "model.ckpt"
================================================
FILE: wechat_jump.py
================================================
# -*- coding: utf-8 -*-
from __future__ import print_function, division
import os
import time
import datetime
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import cv2
VERSION = "1.1.4"
scale = 0.25
template = cv2.imread('./resource/image/character.png')
template = cv2.resize(template, (0, 0), fx=scale, fy=scale)
template_size = template.shape[:2]
def search(img):
result = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
cv2.rectangle(
img,
(min_loc[0], min_loc[1]),
(min_loc[0] + template_size[1], min_loc[1] + template_size[0]),
(255, 0, 0),
4)
return img, min_loc[0] + template_size[1] / 2, min_loc[1] + template_size[0]
def pull_screenshot():
filename = datetime.datetime.now().strftime("%H%M%S") + '.png'
os.system('mv autojump.png {}'.format(filename))
os.system('adb shell screencap -p /sdcard/autojump.png')
os.system('adb pull /sdcard/autojump.png ./autojump.png')
def jump(distance):
press_time = distance * 1.35
press_time = int(press_time)
cmd = 'adb shell input swipe 320 410 320 410 ' + str(press_time)
print(cmd)
os.system(cmd)
def update_data():
global src_x, src_y
img = cv2.imread('./autojump.png')
img = cv2.resize(img, (0, 0), fx=scale, fy=scale)
img, src_x, src_y = search(img)
return img
fig = plt.figure()
pull_screenshot()
img = update_data()
im = plt.imshow(img, animated=True)
update = True
def updatefig(*args):
global update
if update:
time.sleep(1)
pull_screenshot()
im.set_array(update_data())
update = False
return im,
def on_click(event):
global update
global src_x, src_y
dst_x, dst_y = event.xdata, event.ydata
distance = (dst_x - src_x)**2 + (dst_y - src_y)**2
distance = (distance ** 0.5) / scale
print('distance = ', distance)
jump(distance)
update = True
fig.canvas.mpl_connect('button_press_event', on_click)
ani = animation.FuncAnimation(fig, updatefig, interval=5, blit=True)
plt.show()
================================================
FILE: wechat_jump_auto.py
================================================
# -*- coding: utf-8 -*-
"""
=== 思路 ===
核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
根据两个点的距离乘以一个时间系数获得长按的时间
识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条
直线,就从上往下一行一行遍历,比较颜色(颜色用了一个区间来比较)
找到最下面的那一行的所有点,然后求个中点,求好之后再让 Y 轴坐标
减小棋子底盘的一半高度从而得到中心点的坐标
识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,
由于圆形的块最顶上是一条线,方形的上面大概是一个点,所以就
用类似识别棋子的做法多识别了几个点求中点,这时候得到了块中点的 X
轴坐标,这时候假设现在棋子在当前块的中心,根据一个通过截图获取的
固定的角度来推出中点的 Y 坐标
最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
"""
import math
import re
import random
import sys
import time
from PIL import Image
from six.moves import input
if sys.version_info.major != 3:
print('请使用Python3')
exit(1)
try:
from common import debug, config, screenshot, UnicodeStreamFilter
from common.auto_adb import auto_adb
except Exception as ex:
print(ex)
print('请将脚本放在项目根目录中运行')
print('请检查项目根目录中的 common 文件夹是否存在')
exit(1)
adb = auto_adb()
VERSION = "1.1.4"
# DEBUG 开关,需要调试的时候请改为 True,不需要调试的时候为 False
DEBUG_SWITCH = False
adb.test_device()
# 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']
# 图形中圆球的直径,可以利用系统自带画图工具,用直线测量像素,如果可以实现自动识别圆球直径,那么此处将可实现全自动。
head_diameter = config.get('head_diameter')
if head_diameter == None:
density_str = adb.test_density()
matches = re.search(r'\d+', density_str)
density_val = int(matches.group(0))
head_diameter = density_val / 8
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 - 200, left + 200))
top = int(random.uniform(top - 200, top + 200)) # 随机防 ban
after_top = int(random.uniform(top - 200, top + 200))
after_left = int(random.uniform(left - 200, left + 200))
swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, after_left, after_top
def jump(distance, delta_piece_y):
"""
跳跃一定的距离
"""
# 计算程序长度与截图测得的距离的比例
scale = 0.945 * 2 / head_diameter
actual_distance = distance * scale * (math.sqrt(6) / 2)
press_time = (-945 + math.sqrt(945 ** 2 + 4 * 105 *
36 * actual_distance)) / (2 * 105) * 1000
press_time *= press_coefficient
press_time = max(press_time, 200) # 设置 200ms 是最小的按压时间
press_time = int(press_time)
cmd = 'shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(
x1=swipe_x1,
y1=swipe_y1,
x2=swipe_x2,
y2=swipe_y2,
duration=press_time + delta_piece_y
)
print(cmd)
adb.run(cmd)
return press_time
def find_piece_and_board(im):
"""
寻找关键坐标
"""
w, h = im.size
points = [] # 所有满足色素的点集合
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(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 pixel != last_pixel:
scan_start_y = i - 50
break
if scan_start_y:
break
print('start scan Y axis: {}'.format(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):
points.append((j, i))
piece_y_max = max(i, piece_y_max)
bottom_x = [x for x, y in points if y == piece_y_max] # 所有最底层的点的横坐标
if not bottom_x:
return 0, 0, 0, 0, 0
piece_x = int(sum(bottom_x) / len(bottom_x)) # 中间值
piece_y = piece_y_max - piece_base_height_1_2 # 上移棋子底盘高度的一半
# 限制棋盘扫描的横坐标,避免音符 bug
if piece_x < w / 2:
board_x_start = piece_x
board_x_end = w
else:
board_x_start = 0
board_x_end = piece_x
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(int(board_x_start), int(board_x_end)):
pixel = im_pixel[j, i]
# 修掉脑袋比下一个小格子还高的情况的 bug
if abs(j - piece_x) < piece_body_width:
continue
# 检查Y轴下面5个像素, 和背景色相同, 那么是干扰
ver_pixel = im_pixel[j, i + 5]
if abs(pixel[0] - last_pixel[0]) \
+ abs(pixel[1] - last_pixel[1]) \
+ abs(pixel[2] - last_pixel[2]) > 10 \
and abs(ver_pixel[0] - last_pixel[0]) \
+ abs(ver_pixel[1] - last_pixel[1]) \
+ abs(ver_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
last_pixel = im_pixel[board_x, i]
# 首先找到游戏的对称中心,由对称中心做辅助线与x=board_x直线的交点即为棋盘的中心位置
# 有了对称中心,可以知道棋子在棋盘上面的相对位置(偏高或偏低,偏高的话测量值比实际值大,
# 偏低相反。最后通过delta_piece_y来对跳跃时间进行微调
center_x = w / 2 + (24 / 1080) * w
center_y = h / 2 + (17 / 1920) * h
if piece_x > center_x:
board_y = round((25.5 / 43.5) * (board_x - center_x) + center_y)
delta_piece_y = piece_y - round((25.5 / 43.5) * (piece_x - center_x) + center_y)
else:
board_y = round(-(25.5 / 43.5) * (board_x - center_x) + center_y)
delta_piece_y = piece_y - round(-(25.5 / 43.5) * (piece_x - center_x) + center_y)
if not all((board_x, board_y)):
return 0, 0, 0, 0, 0
return piece_x, piece_y, board_x, board_y, delta_piece_y
def yes_or_no():
"""
检查是否已经为启动程序做好了准备
"""
while True:
yes_or_no = str(input('请确保手机打开了 ADB 并连接了电脑,'
'然后打开跳一跳并【开始游戏】后再用本程序,确定开始?[y/n]:'))
if yes_or_no == 'y':
break
elif yes_or_no == 'n':
print('谢谢使用', end='')
exit(0)
else:
print('请重新输入')
def main():
"""
主函数
"""
print('程序版本号:{}'.format(VERSION))
print('激活窗口并按 CONTROL + C 组合键退出')
debug.dump_device_info()
screenshot.check_screenshot()
i, next_rest, next_rest_time = (0, random.randrange(3, 10),
random.randrange(5, 10))
while True:
im = screenshot.pull_screenshot()
# 获取棋子和 board 的位置
piece_x, piece_y, board_x, board_y, delta_piece_y = find_piece_and_board(im)
ts = int(time.time())
print(ts, piece_x, piece_y, board_x, board_y)
set_button_position(im)
jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2), delta_piece_y)
if DEBUG_SWITCH:
debug.save_debug_screenshot(ts, im, piece_x,
piece_y, board_x, board_y)
debug.backup_screenshot(ts)
im.close()
i += 1
if i == next_rest:
print('已经连续打了 {} 下,休息 {}秒'.format(i, next_rest_time))
for j in range(next_rest_time):
sys.stdout.write('\r程序将在 {}秒 后继续'.format(next_rest_time - j))
sys.stdout.flush()
time.sleep(1)
print('\n继续')
i, next_rest, next_rest_time = (0, random.randrange(30, 100),
random.randrange(10, 60))
# 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
time.sleep(random.uniform(1.2, 1.4))
if __name__ == '__main__':
try:
yes_or_no()
main()
except KeyboardInterrupt:
adb.run('kill-server')
print('\n谢谢使用', end='')
exit(0)
================================================
FILE: wechat_jump_auto_ai.py
================================================
# coding: utf-8
'''
# === 思路 ===
# 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
# 根据两个点的距离乘以一个时间系数获得长按的时间
# 识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条直线,就从上往下一行一行遍历,
# 比较颜色(颜色用了一个区间来比较)找到最下面的那一行的所有点,然后求个中点,
# 求好之后再让 Y 轴坐标减小棋子底盘的一半高度从而得到中心点的坐标
# 识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,由于圆形的块最顶上是一条线,
# 方形的上面大概是一个点,所以就用类似识别棋子的做法多识别了几个点求中点,
# 这时候得到了块中点的 X 轴坐标,这时候假设现在棋子在当前块的中心,
# 根据一个通过截图获取的固定的角度来推出中点的 Y 坐标
# 最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
'''
import os
import sys
import subprocess
import time
import math
import pandas
from PIL import Image
import random
from six.moves import input
try:
from common import ai, debug, config, UnicodeStreamFilter
from common.auto_adb import auto_adb
except Exception as ex:
print(ex)
print('请将脚本放在项目根目录中运行')
print('请检查项目根目录中的 common 文件夹是否存在')
exit(1)
adb = auto_adb()
VERSION = "1.1.4"
debug_switch = True # debug 开关,需要调试的时候请改为:True
config = config.open_accordant_config()
# Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需设置,设置保存在 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'] # 棋子的宽度,比截图中量到的稍微大一点比较安全,可能要调节
screenshot_way = 2
def pull_screenshot():
process = subprocess.Popen('adb shell screencap -p', shell=True, stdout=subprocess.PIPE)
screenshot = process.stdout.read()
if sys.platform == 'win32':
screenshot = screenshot.replace(b'\r\n', b'\n')
f = open('autojump.png', 'wb')
f.write(screenshot)
f.close()
def pull_screenshot_temp():
process = subprocess.Popen('adb shell screencap -p', shell=True, stdout=subprocess.PIPE)
screenshot = process.stdout.read()
if sys.platform == 'win32':
screenshot = screenshot.replace(b'\r\n', b'\n')
f = open('autojump_temp.png', 'wb')
f.write(screenshot)
f.close()
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 - 100, left + 100))
top = int(random.uniform(top - 100, top + 100)) # 随机防 ban
after_top = int(random.uniform(top - 100, top + 100))
after_left = int(random.uniform(left - 100, left + 100))
swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, after_left, after_top
def jump(distance):
'''
跳跃一定的距离
'''
if ai.get_result_len() >= 10: # 需采集10条样本以上
k, b, v = ai.computing_k_b_v(distance)
press_time = distance * k[0] + b
print('Y = {k} * X + {b}'.format(k=k[0], b=b))
else:
press_time = distance * press_coefficient
press_time = max(press_time, 200) # 设置 200ms 是最小的按压时间
press_time = int(press_time)
cmd = '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('{}'.format(cmd))
adb.run(cmd)
return press_time
# 转换色彩模式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(im):
'''
寻找关键坐标
'''
w, h = im.size
piece_x_sum = 0
piece_x_c = 0
piece_y_max = 0
scan_x_border = int(w / 8) # 扫描棋子时的左右边界
scan_start_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 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: {}'.format(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,
piece_x = int(piece_x_sum / piece_x_c)
piece_y = piece_y_max - piece_base_height_1_2 # 上移棋子底盘高度的一半
return piece_x, piece_y
def find_piece_and_board(im):
w, h = im.size
piece_x_sum = 0
piece_x_c = 0
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()
# 以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 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]
# 计算阴影的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
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, new_board_y
def check_screenshot():
'''
检查获取截图的方式
'''
global screenshot_way
if os.path.isfile('autojump.png'):
os.remove('autojump.png')
if (screenshot_way < 0):
print('暂不支持当前设备')
sys.exit()
pull_screenshot()
try:
Image.open('./autojump.png').load()
print('采用方式 {} 获取截图'.format(screenshot_way))
except Exception:
screenshot_way -= 1
check_screenshot()
def yes_or_no(prompt, true_value='y', false_value='n', default=True):
default_value = true_value if default else false_value
prompt = '%s %s/%s [%s]: ' % (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 %s or %s: ' % (true_value, false_value)
i = input(prompt)
def main():
'''
主函数
'''
# op = yes_or_no('请确保手机打开了 ADB 并连接了电脑,然后打开跳一跳并【开始游戏】后再用本程序,确定开始?')
# if not op:
# print('bye')
# return
# 初始化AI
ai.init()
print('程序版本号:{}'.format(VERSION))
debug.dump_device_info()
check_screenshot()
i, next_rest, next_rest_time = 0, random.randrange(3, 10), random.randrange(5, 10)
while True:
pull_screenshot()
im = Image.open('./autojump.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)
set_button_position(im)
press_time = jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2))
# 在跳跃落下的瞬间 摄像机移动前截图 这个参数要自己校调
time.sleep(0.2)
pull_screenshot_temp()
im_temp = Image.open('./autojump_temp.png')
temp_piece_x, temp_piece_y = find_piece(im_temp)
debug.computing_error(press_time, board_x, board_y, piece_x, piece_y, temp_piece_x, temp_piece_y)
if debug_switch:
debug.save_debug_screenshot(ts, im, piece_x, piece_y, board_x, board_y)
debug.save_debug_screenshot(ts, im_temp, temp_piece_x, temp_piece_y, board_x, board_y)
# debug.backup_screenshot(ts)
i = 0
if i == next_rest:
print('已经连续打了 {} 下,休息 {}s'.format(i, next_rest_time))
for j in range(next_rest_time):
sys.stdout.write('\r程序将在 {}s 后继续'.format(next_rest_time - j))
sys.stdout.flush()
time.sleep(1)
print('\n继续')
i, next_rest, next_rest_time = 0, random.randrange(30, 100), random.randrange(10, 60)
time.sleep(random.uniform(0.5, 0.6)) # 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
adb.run('kill-server')
print('bye')
exit(0)
================================================
FILE: wechat_jump_auto_curves.py
================================================
# -*- coding: utf-8 -*-
"""
##基于python3.5(64位)
###如果缺少scikit-image库,建议进下面网址下载whl直接安装
##https://www.lfd.uci.edu/~gohlke/pythonlibs/#scikit-image
=== 思路 ===
核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
根据两个点的距离乘以一个时间系数获得长按的时间
识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条
直线,就从上往下一行一行遍历,比较颜色(颜色用了一个区间来比较)
找到最下面的那一行的所有点,然后求个中点,求好之后再让 Y 轴坐标
减小棋子底盘的一半高度从而得到中心点的坐标
识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,
由于圆形的块最顶上是一条线,方形的上面大概是一个点,所以就
用类似识别棋子的做法多识别了几个点求中点,这时候得到了块中点的 X
轴坐标,这时候假设现在棋子在当前块的中心,根据一个通过截图获取的
固定的角度来推出中点的 Y 坐标
最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
"""
from __future__ import print_function, division
import sys
import time
import math
import random
from PIL import Image
from six.moves import input
from skimage import io,transform
import numpy as np
import tensorflow as tf
try:
from common import debug, config, screenshot, UnicodeStreamFilter
from common.auto_adb import auto_adb
except Exception as ex:
print(ex)
print('请将脚本放在项目根目录中运行')
print('请检查项目根目录中的 common 文件夹是否存在')
exit(1)
adb = auto_adb()
VERSION = "1.1.4"
# DEBUG 开关,需要调试的时候请改为 True,不需要调试的时候为 False
DEBUG_SWITCH = False
# 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']
target_score=1024 ##目标分数
total_step=30 ##达到目标次数所需游戏次数
start_score=100 ##设置第一次分数(目前分数)
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 - 100, left + 100))
top = int(random.uniform(top - 100, top + 100)) # 随机防 ban
after_top = int(random.uniform(top - 100, top + 100))
after_left = int(random.uniform(left - 100, left + 100))
swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, after_left, after_top
def jump(distance):
"""
跳跃一定的距离
"""
press_time = distance * press_coefficient
press_time = max(press_time, 200) # 设置 200ms 是最小的按压时间
press_time = int(press_time)
cmd = '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('{} {}'.format(adb.adb_path, cmd))
adb.run(cmd)
return press_time
def find_piece_and_board(im):
"""
寻找关键坐标
"""
w, h = im.size
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(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 pixel != last_pixel:
scan_start_y = i - 50
break
if scan_start_y:
break
print('scan_start_y: {}'.format(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 = int(piece_x_sum / piece_x_c)
piece_y = piece_y_max - piece_base_height_1_2 # 上移棋子底盘高度的一半
# 限制棋盘扫描的横坐标,避免音符 bug
if piece_x < w/2:
board_x_start = piece_x
board_x_end = w
else:
board_x_start = 0
board_x_end = piece_x
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(int(board_x_start), int(board_x_end)):
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
last_pixel = im_pixel[board_x, i]
# 从上顶点往下 +274 的位置开始向上找颜色与上顶点一样的点,为下顶点
# 该方法对所有纯色平面和部分非纯色平面有效,对高尔夫草坪面、木纹桌面、
# 药瓶和非菱形的碟机(好像是)会判断错误
for k in range(i+274, i, -1): # 274 取开局时最大的方块的上下顶点距离
pixel = im_pixel[board_x, k]
if abs(pixel[0] - last_pixel[0]) \
+ abs(pixel[1] - last_pixel[1]) \
+ abs(pixel[2] - last_pixel[2]) < 10:
break
board_y = int((i+k) / 2)
# 如果上一跳命中中间,则下个目标中心会出现 r245 g245 b245 的点,利用这个
# 属性弥补上一段代码可能存在的判断错误
# 若上一跳由于某种原因没有跳到正中间,而下一跳恰好有无法正确识别花纹,则有
# 可能游戏失败,由于花纹面积通常比较大,失败概率较低
for j in range(i, i+200):
pixel = im_pixel[board_x, j]
if abs(pixel[0] - 245) + abs(pixel[1] - 245) + abs(pixel[2] - 245) == 0:
board_y = j + 10
break
if not all((board_x, board_y)):
return 0, 0, 0, 0
return piece_x, piece_y, board_x, board_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 pross_data(image):
pixels = list(image.getdata()) # 得到像素数据 灰度0-255
#print(len(pixels))
for i in range(len(pixels)):
if pixels[i]<100:
pixels[i]=0
else:
pixels[i]=255
return pixels
def pixel_division(img,w,h):
pixels = list(img.getdata())
row_pix=np.zeros([1,h])
col_pix=np.zeros([1,w])
for i in range(w):
for j in range(h):
if pixels[j*w+i]<100:
row_pix[0,j]+=1
col_pix[0,i]+=1
start_h=0
end_h=0
flag=0
for j in range(h):
if row_pix[0,j]>=1 and flag==0:
start_h=j
flag=1
if row_pix[0,j]>=1:
end_h=j
pixels_Widh=[]
end_w=0
for i in range(1,w):
if col_pix[0,i-1]<=0 and col_pix[0,i]>=1:
pixels_Widh.append(i-1)
if col_pix[0,i]>=1:
end_w=i
pixels_Widh.append(end_w+1)
return start_h,end_h,pixels_Widh
def strint(score0):
if(score0<10):
return str(score0)
else:
return ""
def read_one_image(path):
img = io.imread(path)
w=81
h=81
c=1
img = transform.resize(img,(w,h,c))
return np.asarray(img)
def main():
"""
主函数
"""
op = yes_or_no('请确保手机打开了 ADB 并连接了电脑,'
'然后打开跳一跳并【开始游戏】后再用本程序,确定开始?')
if not op:
print('bye')
return
print('程序版本号:{}'.format(VERSION))
debug.dump_device_info()
screenshot.check_screenshot()
i, next_rest, next_rest_time = (0, random.randrange(3, 10),
random.randrange(5, 10))
j= 0
################ 分数曲线公式
y_score=[]
next_start=0
global start_score
for i in range(total_step):
each_score=target_score*(1-np.exp(-0.15*(1024.0/target_score)*i))
y_score.append(each_score)
if start_score>each_score:
next_start=i
next_start+=1
#print(y_score)
if start_score<y_score[0]:
next_start=0
###################
with tf.Session() as sess:
saver = tf.train.import_meta_graph('./resource/model/model.ckpt.meta')
saver.restore(sess,tf.train.latest_checkpoint('./resource/model/'))
graph = tf.get_default_graph()
x = graph.get_tensor_by_name("x:0")
logits = graph.get_tensor_by_name("logits_eval:0")
#####################识别分数
while True:
screenshot.pull_screenshot()
im = Image.open('./autojump.png')
##比例系数
pix_w=im.size[0]*1.0/1080
pix_h=im.size[1]
region=im.crop((0,pix_h*0.1,460*pix_w,pix_h*0.2))
region=region.convert('L')
start_h,end_h,pixels_Widh=pixel_division(region,int(460*pix_w),int(pix_h*0.1))
if start_h==end_h:
continue
data = []
for i in range(len(pixels_Widh)-1):
region1=region.crop((pixels_Widh[i],start_h,pixels_Widh[i+1],end_h))
region1.putdata(pross_data(region1))
str1="./region"+str(i)+".png"
region1.save(str1)
data1 = read_one_image(str1)
data.append(data1)
feed_dict = {x:data}
classification_result = sess.run(logits,feed_dict)
output = []
output = tf.argmax(classification_result,1).eval()
m_score=""
for i in range(len(output)):
m_score+=strint(output[i])
if m_score=="":
continue
m_score=int(m_score)
print('score:{}'.format(m_score))
####################################
# 获取棋子和 board 的位置
print(j)
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)
set_button_position(im)
if m_score > y_score[next_start]: ##自动结束这一次
print("----------------")
jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2)*5)
next_start+=1
time.sleep(5*random.random())
if next_start >len(y_score):
break
jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_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()
i += 1
j += 1
if i == next_rest:
print('已经连续打了 {} 下,休息 {}s'.format(i, next_rest_time))
for j in range(next_rest_time):
sys.stdout.write('\r程序将在 {}s 后继续'.format(next_rest_time - j))
sys.stdout.flush()
time.sleep(1)
print('\n继续')
i, next_rest, next_rest_time = (0, random.randrange(30, 100),
random.randrange(10, 60))
# 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
time.sleep(random.uniform(0.9, 1.2))
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
adb.run('kill-server')
print('bye')
exit(0)
================================================
FILE: wechat_jump_auto_iOS.py
================================================
# -*- coding: utf-8 -*-
"""
# === 思路 ===
# 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
# 根据两个点的距离乘以一个时间系数获得长按的时间
# 识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条
直线,就从上往下一行一行遍历,比较颜色(颜色用了一个区间来比较)
找到最下面的那一行的所有点,然后求个中点,求好之后再让 Y 轴坐标
减小棋子底盘的一半高度从而得到中心点的坐标
# 识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,
由于圆形的块最顶上是一条线,方形的上面大概是一个点,所以就
用类似识别棋子的做法多识别了几个点求中点,这时候得到了块中点的 X
轴坐标,这时候假设现在棋子在当前块的中心,根据一个通过截图获取的
固定的角度来推出中点的 Y 坐标
# 最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
"""
import os
import shutil
import time
import math
import random
import json
from PIL import Image, ImageDraw
import wda
with open('config.json', 'r') as f:
config = json.load(f)
# Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需设置
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']
time_coefficient = config['press_coefficient']
# 模拟按压的起始点坐标,需要自动重复游戏请设置成“再来一局”的坐标
swipe = config.get('swipe', {
"x1": 320,
"y1": 410,
"x2": 320,
"y2": 410
})
VERSION = "1.1.4"
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(random.uniform(0, 320), random.uniform(64, 320), 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 = piece_x_c = piece_y_max = 0
board_x = 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 != last_pixel:
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)
# 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
time.sleep(random.uniform(1, 1.1))
if __name__ == '__main__':
main()
================================================
FILE: wechat_jump_auto_slim.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'Erimus'
'''
这个是精简版本,只取x轴距离。
可以适配任意屏幕。
把磁盘读写截图改为内存读写。
可以防止被ban(从抓包数据看没有返回Error)。
'''
import os
import sys
import subprocess
import time
import random
from PIL import Image, ImageDraw
from io import BytesIO
# ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
VERSION = "1.1.4"
screenshot_way = 2
def check_screenshot(): # 检查获取截图的方式
global screenshot_way
if (screenshot_way < 0):
print('暂不支持当前设备')
sys.exit()
binary_screenshot = pull_screenshot()
try:
Image.open(BytesIO(binary_screenshot)).load() # 直接使用内存IO
print('Capture Method: {}'.format(screenshot_way))
except Exception:
screenshot_way -= 1
check_screenshot()
def pull_screenshot(): # 获取截图
global screenshot_way
if screenshot_way in [1, 2]:
process = subprocess.Popen(
'adb shell screencap -p', shell=True, stdout=subprocess.PIPE)
screenshot = process.stdout.read()
if screenshot_way == 2:
binary_screenshot = screenshot.replace(b'\r\n', b'\n')
else:
binary_screenshot = screenshot.replace(b'\r\r\n', b'\n')
return binary_screenshot
elif screenshot_way == 0:
os.system('adb shell screencap -p /sdcard/autojump.png')
os.system('adb pull /sdcard/autojump.png .')
# ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
def find_piece_and_board(im): # 寻找起点和终点坐标
w, h = im.size # 图片宽高
im_pixel = im.load()
def find_piece(pixel): # 棋子取色精确范围
return ((40 < pixel[0] < 65) and
(40 < pixel[1] < 65) and
(80 < pixel[2] < 105))
# 寻找棋子 ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
# 粗查棋子位置
piece_found, piece_fx, piece_fy = 0, 0, 0
scan_piece_unit = w // 40 # 间隔单位
ny = (h + w) // 2 # 寻找下限 从画面中央的正方形的下缘开始
while ny > (h - w) // 2 and not piece_found:
ny -= scan_piece_unit
for nx in range(0, w, scan_piece_unit):
pixel = im_pixel[nx, ny]
if find_piece(pixel):
piece_fx, piece_fy = nx, ny
piece_found = True
break
print('%-12s %s,%s' % ('piece_fuzzy:', piece_fx, piece_fy))
if not piece_fx:
return 0, 0 # 没找到棋子
# 精查棋子位置
piece_x, piece_x_set = 0, [] # 棋子x/棋子坐标集合
piece_width = w // 14 # 估算棋子宽度
piece_height = w // 5 # 估算棋子高度
for ny in range(piece_fy + scan_piece_unit, piece_fy - piece_height, -4):
for nx in range(max(piece_fx - piece_width, 0),
min(piece_fx + piece_width, w)):
pixel = im_pixel[nx, ny]
# print(nx,ny,pixel)
if find_piece(pixel):
piece_x_set.append(nx)
if len(piece_x_set) > 10:
piece_x = sum(piece_x_set) / len(piece_x_set)
break
print('%-12s %s' % ('p_exact_x:', piece_x))
# 寻找落点 ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
board_x = 0
# 限制棋盘扫描的横坐标 避免音符bug
if piece_x < w / 2:
board_x_start, board_x_end = w // 2, w # 起点和终点的中点是画面中心
else:
board_x_start, board_x_end = 0, w // 2
# 寻找落点顶点
board_x_set = [] # 目标坐标集合/改为list避免去重
for by in range((h - w) // 2, (h + w) // 2, 4):
bg_pixel = im_pixel[0, by]
for bx in range(board_x_start, board_x_end):
pixel = im_pixel[bx, by]
# 修掉脑袋比下一个小格子还高的情况 屏蔽小人左右的范围
if abs(bx - piece_x) < piece_width:
continue
# 修掉圆顶的时候一条线导致的小bug 这个颜色判断应该OK
if (abs(pixel[0] - bg_pixel[0]) +
abs(pixel[1] - bg_pixel[1]) +
abs(pixel[2] - bg_pixel[2]) > 10):
board_x_set.append(bx)
if len(board_x_set) > 10:
board_x = sum(board_x_set) / len(board_x_set)
print('%-12s %s' % ('target_x:', board_x))
break # 找到了退出
return piece_x, board_x
# ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
def set_button_position(im, gameover=0): # 重设点击位置 再来一局位置
w, h = im.size
if h // 16 > w // 9 + 2: # 长窄屏 2px容差 获取ui描绘的高度
uih = int(w / 9 * 16)
else:
uih = h
# uiw = int(uih / 16 * 9)
# 如果游戏结束 点击再来一局
left = int(w / 2) # 按钮半宽约uiw//5
# 根据9:16实测按钮高度中心0.825 按钮半高约uiw//28
top = int((h - uih) / 2 + uih * 0.825)
if gameover:
return left, top
# 游戏中点击 随机位置防 ban
left = random.randint(w // 4, w - 20) # 避开左下角按钮
top = random.randint(h * 3 // 4, h - 20)
return left, top
# ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
def jump(piece_x, board_x, im, swipe_x1, swipe_y1):
distanceX = abs(board_x - piece_x) # 起点到目标的水平距离
shortEdge = min(im.size) # 屏幕宽度
jumpPercent = distanceX / shortEdge # 跳跃百分比
jumpFullWidth = 1700 # 跳过整个宽度 需要按压的毫秒数
press_time = round(jumpFullWidth * jumpPercent) # 按压时长
press_time = 0 if not press_time else max(
press_time, 200) # press_time大于0时限定最小值
print('%-12s %.2f%% (%s/%s) | Press: %sms' %
('Distance:', jumpPercent * 100, distanceX, shortEdge, press_time))
cmd = 'adb shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(
x1=swipe_x1,
y1=swipe_y1,
x2=swipe_x1 + random.randint(-10, 10), # 模拟位移
y2=swipe_y1 + random.randint(-10, 10),
duration=press_time
)
os.system(cmd)
# ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
def main():
check_screenshot() # 检查截图
count = 0
while True:
count += 1
print('---\n%-12s %s (%s)' % ('Times:', count, int(time.time())))
# 获取截图
binary_screenshot = pull_screenshot()
im = Image.open(BytesIO(binary_screenshot))
w, h = im.size
if w > h:
im = im.rotate(-90, expand=True) # 添加图片方向判断
# print('image | w:%s | h:%s'%(w,h))
# 获取棋子和 board 的位置
piece_x, board_x = find_piece_and_board(im)
gameover = 0 if all((piece_x, board_x)) else 1
swipe_x1, swipe_y1 = set_button_position(
im, gameover=gameover) # 随机点击位置
# 标注截图并显示
# draw = ImageDraw.Draw(im)
# draw.line([piece_x, 0, piece_x, h], fill='blue', width=1) # start
# draw.line([board_x, 0, board_x, h], fill='red', width=1) # end
# draw.ellipse([swipe_x1 - 16, swipe_y1 - 16,
# swipe_x1 + 16, swipe_y1 + 16], fill='red') # click
# im.show()
jump(piece_x, board_x, im, swipe_x1, swipe_y1)
wait = (random.random())**5 * 9 + 1 # 停1~9秒 指数越高平均间隔越短
print('---\nWait %.3f s...' % wait)
time.sleep(wait)
print('Continue!')
# ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
os.system('adb kill-server')
print('bye')
exit(0)
================================================
FILE: wechat_jump_iOS_py3.py
================================================
# -*- coding: utf-8 -*-
import time
import wda
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from PIL import Image
# 截图距离 * time_coefficient = 按键时长
# time_coefficient:
# iphonex: 0.00125
# iphone6: 0.00196
# iphone6s plus: 0.00120
time_coefficient = 0.00120
VERSION = "1.1.4"
c = wda.Client()
s = c.session()
def pull_screenshot():
c.screenshot('autojump.png')
def jump(distance):
press_time = distance * time_coefficient
press_time = press_time
print('press_time = ',press_time)
s.tap_hold(200, 200, press_time)
fig = plt.figure()
pull_screenshot()
img = np.array(Image.open('autojump.png'))
im = plt.imshow(img, animated=True)
update = True
click_count = 0
cor = []
def update_data():
return np.array(Image.open('autojump.png'))
def updatefig(*args):
global update
if update:
time.sleep(1)
pull_screenshot()
im.set_array(update_data())
update = False
return im,
def on_click(event):
global update
global ix, iy
global click_count
global cor
ix, iy = event.xdata, event.ydata
coords = [(ix, iy)]
print('now = ', coords)
cor.append(coords)
click_count += 1
if click_count > 1:
click_count = 0
cor1 = cor.pop()
cor2 = cor.pop()
distance = (cor1[0][0] - cor2[0][0])**2 + (cor1[0][1] - cor2[0][1])**2
distance = distance ** 0.5
print('distance = ', distance)
jump(distance)
update = True
fig.canvas.mpl_connect('button_press_event', on_click)
ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True)
plt.show()
================================================
FILE: wechat_jump_py3.py
================================================
# -*- coding: utf-8 -*-
import os
import time
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from PIL import Image
VERSION = "1.1.4"
def pull_screenshot():
os.system('adb shell screencap -p /sdcard/autojump.png')
os.system('adb pull /sdcard/autojump.png .')
def jump(distance):
press_time = distance * 1.35
press_time = int(press_time)
cmd = 'adb shell input swipe 320 410 320 410 ' + str(press_time)
print(cmd)
os.system(cmd)
fig = plt.figure()
pull_screenshot()
img = np.array(Image.open('autojump.png'))
im = plt.imshow(img, animated=True)
update = True
click_count = 0
cor = []
def update_data():
return np.array(Image.open('autojump.png'))
def updatefig(*args):
global update
if update:
time.sleep(1.5)
pull_screenshot()
im.set_array(update_data())
update = False
return im,
def on_click(event):
global update
global ix, iy
global click_count
global cor
ix, iy = event.xdata, event.ydata
coords = [(ix, iy)]
print('now = ', coords)
cor.append(coords)
click_count += 1
if click_count > 1:
click_count = 0
cor1 = cor.pop()
cor2 = cor.pop()
distance = (cor1[0][0] - cor2[0][0])**2 + (cor1[0][1] - cor2[0][1])**2
distance = distance ** 0.5
print('distance = ', distance)
jump(distance)
update = True
fig.canvas.mpl_connect('button_press_event', on_click)
ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True)
plt.show()
gitextract_rovqql80/ ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README.rst ├── Tools/ │ └── README.md ├── changelog.md ├── common/ │ ├── UnicodeStreamFilter.py │ ├── __init__.py │ ├── ai.py │ ├── auto_adb.py │ ├── config.py │ ├── debug.py │ └── screenshot.py ├── config/ │ ├── 1280x720/ │ │ └── config.json │ ├── 1440x720/ │ │ └── config.json │ ├── 1800x1080/ │ │ └── config.json │ ├── 1920x1080/ │ │ └── config.json │ ├── 2160x1080/ │ │ └── config.json │ ├── 2560x1440/ │ │ └── config.json │ ├── 720x1280 │ ├── 960x540/ │ │ └── config.json │ ├── default.json │ ├── huawei/ │ │ ├── honorV8 │ │ └── honor_note8_config.json │ ├── iPhone/ │ │ ├── 6_config.json │ │ ├── 8P_7P_6sP_6P_config.json │ │ ├── 8_config.json │ │ ├── SE_config.json │ │ └── X_config.json │ ├── mi/ │ │ ├── max2_config.json │ │ ├── mi5_config.json │ │ ├── mi5s_config.json │ │ ├── mi5x_config.json │ │ ├── mi6_config.json │ │ ├── mix2_config.json │ │ └── note2_config.json │ ├── samsung/ │ │ ├── s7edge_config.json │ │ ├── s8.json │ │ └── s8在设置里关闭曲面侧屏 │ └── smartisan/ │ └── pro2_config.json ├── jump_bot/ │ ├── Makefile │ ├── README.rst │ ├── jumpbot/ │ │ ├── algos.py │ │ ├── auto.py │ │ ├── bot.py │ │ ├── connector.py │ │ ├── manual.py │ │ └── settings.py │ └── requirements.txt ├── requirements.txt ├── resource/ │ └── model/ │ ├── checkpoint │ ├── model.ckpt.data-00000-of-00001 │ ├── model.ckpt.index │ └── model.ckpt.meta ├── wechat_jump.py ├── wechat_jump_auto.py ├── wechat_jump_auto_ai.py ├── wechat_jump_auto_curves.py ├── wechat_jump_auto_iOS.py ├── wechat_jump_auto_slim.py ├── wechat_jump_iOS_py3.py └── wechat_jump_py3.py
SYMBOL INDEX (104 symbols across 20 files)
FILE: common/UnicodeStreamFilter.py
class UnicodeStreamFilter (line 5) | class UnicodeStreamFilter:
method __init__ (line 7) | def __init__(self, target):
function write (line 14) | def write(self, s):
FILE: common/ai.py
function linear_model_main (line 10) | def linear_model_main(_distances, _press_times, target_distance):
function computing_k_b_v (line 24) | def computing_k_b_v(target_distance):
function add_data (line 32) | def add_data(distance, press_time):
function save_data (line 38) | def save_data(file_name, distances, press_times):
function get_data (line 44) | def get_data(file_name):
function init (line 54) | def init():
function get_result_len (line 66) | def get_result_len():
FILE: common/auto_adb.py
class auto_adb (line 7) | class auto_adb():
method __init__ (line 8) | def __init__(self):
method get_screen (line 33) | def get_screen(self):
method run (line 38) | def run(self, raw_command):
method test_device (line 44) | def test_device(self):
method test_density (line 60) | def test_density(self):
method test_device_detail (line 65) | def test_device_detail(self):
method test_device_os (line 70) | def test_device_os(self):
method adb_path (line 75) | def adb_path(self):
FILE: common/config.py
function open_accordant_config (line 15) | def open_accordant_config():
function _get_screen_size (line 45) | def _get_screen_size():
FILE: common/debug.py
function make_debug_dir (line 29) | def make_debug_dir(screenshot_backup_dir):
function backup_screenshot (line 37) | def backup_screenshot(ts):
function save_debug_screenshot (line 47) | def save_debug_screenshot(ts, im, piece_x, piece_y, board_x, board_y):
function computing_error (line 66) | def computing_error(last_press_time, target_board_x, target_board_y, las...
function dump_device_info (line 83) | def dump_device_info():
FILE: common/screenshot.py
function pull_screenshot (line 23) | def pull_screenshot():
function check_screenshot (line 45) | def check_screenshot():
FILE: jump_bot/jumpbot/algos.py
function get_press_time (line 3) | def get_press_time(piece_x, piece_y, board_x, board_y, time_coeff):
FILE: jump_bot/jumpbot/auto.py
class AutoBot (line 12) | class AutoBot(Connector):
method __init__ (line 14) | def __init__(self, params=settings.get_bot_params()):
method run (line 28) | def run(self):
method _get_coord_y_start_scan (line 55) | def _get_coord_y_start_scan(self, image):
method _find_piece (line 73) | def _find_piece(self, image, coord_y_start_scan):
method _find_board (line 96) | def _find_board(self, image, piece_x, piece_y):
method _set_button_coords (line 128) | def _set_button_coords(self, image):
FILE: jump_bot/jumpbot/bot.py
function config (line 8) | def config():
function jumpbot (line 23) | def jumpbot(parser):
FILE: jump_bot/jumpbot/connector.py
class Connector (line 6) | class Connector:
method __init__ (line 9) | def __init__(self, image_dir=settings.IMAGE_DIR):
method connector_screenshot (line 16) | def connector_screenshot(self):
method connector_taphold (line 20) | def connector_taphold(self, value):
FILE: jump_bot/jumpbot/manual.py
class ManualBot (line 14) | class ManualBot(Connector):
method __init__ (line 16) | def __init__(self, params=settings.get_bot_params()):
method run (line 33) | def run(self):
method action (line 38) | def action(self):
method _onclick (line 44) | def _onclick(self, event):
method _update_figure (line 69) | def _update_figure(self, *args):
method _read_image (line 78) | def _read_image(self):
FILE: jump_bot/jumpbot/settings.py
function get_bot_params (line 25) | def get_bot_params(model="ip"):
FILE: wechat_jump.py
function search (line 18) | def search(img):
function pull_screenshot (line 31) | def pull_screenshot():
function jump (line 38) | def jump(distance):
function update_data (line 46) | def update_data():
function updatefig (line 63) | def updatefig(*args):
function on_click (line 74) | def on_click(event):
FILE: wechat_jump_auto.py
function set_button_position (line 63) | def set_button_position(im):
function jump (line 78) | def jump(distance, delta_piece_y):
function find_piece_and_board (line 103) | def find_piece_and_board(im):
function yes_or_no (line 200) | def yes_or_no():
function main (line 216) | def main():
FILE: wechat_jump_auto_ai.py
function pull_screenshot (line 48) | def pull_screenshot():
function pull_screenshot_temp (line 58) | def pull_screenshot_temp():
function set_button_position (line 68) | def set_button_position(im):
function jump (line 83) | def jump(distance):
function hsv2rgb (line 110) | def hsv2rgb(h, s, v):
function rgb2hsv (line 139) | def rgb2hsv(r, g, b):
function find_piece (line 160) | def find_piece(im):
function find_piece_and_board (line 203) | def find_piece_and_board(im):
function check_screenshot (line 338) | def check_screenshot():
function yes_or_no (line 357) | def yes_or_no(prompt, true_value='y', false_value='n', default=True):
function main (line 372) | def main():
FILE: wechat_jump_auto_curves.py
function set_button_position (line 66) | def set_button_position(im):
function jump (line 82) | def jump(distance):
function find_piece_and_board (line 102) | def find_piece_and_board(im):
function yes_or_no (line 205) | def yes_or_no(prompt, true_value='y', false_value='n', default=True):
function pross_data (line 223) | def pross_data(image):
function pixel_division (line 233) | def pixel_division(img,w,h):
function strint (line 262) | def strint(score0):
function read_one_image (line 268) | def read_one_image(path):
function main (line 276) | def main():
FILE: wechat_jump_auto_iOS.py
function pull_screenshot (line 58) | def pull_screenshot():
function jump (line 62) | def jump(distance):
function backup_screenshot (line 68) | def backup_screenshot(ts):
function save_debug_creenshot (line 77) | def save_debug_creenshot(ts, im, piece_x, piece_y, board_x, board_y):
function set_button_position (line 95) | def set_button_position(im):
function find_piece_and_board (line 106) | def find_piece_and_board(im):
function main (line 185) | def main():
FILE: wechat_jump_auto_slim.py
function check_screenshot (line 24) | def check_screenshot(): # 检查获取截图的方式
function pull_screenshot (line 38) | def pull_screenshot(): # 获取截图
function find_piece_and_board (line 56) | def find_piece_and_board(im): # 寻找起点和终点坐标
function set_button_position (line 133) | def set_button_position(im, gameover=0): # 重设点击位置 再来一局位置
function jump (line 156) | def jump(piece_x, board_x, im, swipe_x1, swipe_y1):
function main (line 179) | def main():
FILE: wechat_jump_iOS_py3.py
function pull_screenshot (line 21) | def pull_screenshot():
function jump (line 25) | def jump(distance):
function update_data (line 42) | def update_data():
function updatefig (line 46) | def updatefig(*args):
function on_click (line 56) | def on_click(event):
FILE: wechat_jump_py3.py
function pull_screenshot (line 10) | def pull_screenshot():
function jump (line 15) | def jump(distance):
function update_data (line 33) | def update_data():
function updatefig (line 37) | def updatefig(*args):
function on_click (line 47) | def on_click(event):
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (109K chars).
[
{
"path": ".github/CONTRIBUTING.md",
"chars": 0,
"preview": ""
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 549,
"preview": "<!--\n感谢提交问题,提交 issue 前请先通过关键字搜索已经存在或解决了的 issue,避免重复提交相同内容。\n请说清楚自己遇到了什么问题,不然可能被 close。\n还没有自己机型配置文件的请自己去创建一个,好用的话欢迎 PR。\n\n#"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 407,
"preview": "<!--\n感谢您的 pull request!\n\n## 在 PR 前请尽量做到:\n- PR 应基于最新的 dev 分支\n```\n git remote add wangshub https://github.com/wangshub/we"
},
{
"path": ".gitignore",
"chars": 1333,
"preview": "# user-defined\ntest/\npic/\nscreenshot_backups/\nautojump.png\nautojump_temp.png\nregion*.png\njump_range.csv\n\n# Byte-compiled"
},
{
"path": "LICENSE",
"chars": 11361,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 52,
"preview": "run:\n\tmkdir -p jumpbot/data\n\tpython3 jumpbot/bot.py\n"
},
{
"path": "README.md",
"chars": 2180,
"preview": "# 教你用 Python 来玩微信跳一跳\n[](https://github"
},
{
"path": "README.rst",
"chars": 2951,
"preview": "##############################################################################\nWechat Jump Bot (iOS)\n###################"
},
{
"path": "Tools/README.md",
"chars": 356,
"preview": "## 所有实验都在该文件夹下运行即可,已放上测试用的代码。免去配置 adb 的麻烦\n\n- 复制 wechat_jump_game 根目录下的 config 文件夹以及 wechat_jump_py3.py 文件到本目录下\n- 按住 `shi"
},
{
"path": "changelog.md",
"chars": 519,
"preview": "## 更新日志\n- 2018-1-8\n - 发布 release 一键启动 app V1.1.2 版本,针对腾讯 ban 方法进行反 ban 操作,详见 [STOP_jump](https://github.com/wangshub/we"
},
{
"path": "common/UnicodeStreamFilter.py",
"chars": 589,
"preview": "# -*- coding: utf-8 -*-\nimport sys\n\nif sys.version_info.major != 3:\n class UnicodeStreamFilter:\n\n def __init__"
},
{
"path": "common/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "common/ai.py",
"chars": 1780,
"preview": "# coding: utf-8\n\n# Copyright (c) 2018 BeiTown\n\nimport os\nimport pandas\nfrom sklearn.linear_model import LinearRegression"
},
{
"path": "common/auto_adb.py",
"chars": 2493,
"preview": "# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nimport platform\n\n\nclass auto_adb():\n def __init__(self):\n "
},
{
"path": "common/config.py",
"chars": 1277,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"\n调取配置文件和屏幕分辨率的代码\n\"\"\"\nimport os\nimport sys\nimport json\nimport re\n\nfrom common.auto_adb import "
},
{
"path": "common/debug.py",
"chars": 3349,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"\n这是debug的代码,当DEBUG_SWITCH开关开启的时候,会将各种信息存在本地,方便检查故障\n\"\"\"\nimport os\nimport sys\nimport shutil\nimp"
},
{
"path": "common/screenshot.py",
"chars": 1677,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"\n手机屏幕截图的代码\n\"\"\"\nimport subprocess\nimport os\nimport sys\nfrom PIL import Image\nfrom io import St"
},
{
"path": "config/1280x720/config.json",
"chars": 226,
"preview": "{\n \"under_game_score_y\": 200,\n \"press_coefficient\": 2.099,\n \"piece_base_height_1_2\": 13,\n \"piece_body_width\""
},
{
"path": "config/1440x720/config.json",
"chars": 219,
"preview": "{\n \"under_game_score_y\": 200,\n \"press_coefficient\": 2.099,\n \"piece_base_height_1_2\": 13,\n \"piece_body_width\""
},
{
"path": "config/1800x1080/config.json",
"chars": 223,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.2,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\": "
},
{
"path": "config/1920x1080/config.json",
"chars": 251,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.392,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\""
},
{
"path": "config/2160x1080/config.json",
"chars": 128,
"preview": "{\n \"under_game_score_y\": 420,\n \"press_coefficient\": 1.372, \n \"piece_base_height_1_2\": 25,\n \"piece_body_width"
},
{
"path": "config/2560x1440/config.json",
"chars": 225,
"preview": "{\n \"under_game_score_y\": 410,\n \"press_coefficient\": 1.475,\n \"piece_base_height_1_2\": 28,\n \"piece_body_width\""
},
{
"path": "config/720x1280",
"chars": 220,
"preview": "{\n \"under_game_score_y\": 369,\n \"press_coefficient\": 2.003,\n \"piece_base_height_1_2\": 13,\n \"piece_body_width"
},
{
"path": "config/960x540/config.json",
"chars": 130,
"preview": "{\n \"under_game_score_y\": 300, \n \"press_coefficient\": 2.732, \n \"piece_base_height_1_2\": 20, \n \"piece_body_wid"
},
{
"path": "config/default.json",
"chars": 130,
"preview": "{\n \"under_game_score_y\": 300, \n \"press_coefficient\": 1.392, \n \"piece_base_height_1_2\": 20, \n \"piece_body_wid"
},
{
"path": "config/huawei/honorV8",
"chars": 226,
"preview": "{\n \"under_game_score_y\": 400,\n \"press_coefficient\": 1.07,\n \"piece_base_height_1_2\": 90,\n \"piece_body_width\":"
},
{
"path": "config/huawei/honor_note8_config.json",
"chars": 226,
"preview": "{\n \"under_game_score_y\": 400,\n \"press_coefficient\": 1.04,\n \"piece_base_height_1_2\": 90,\n \"piece_body_width\":"
},
{
"path": "config/iPhone/6_config.json",
"chars": 225,
"preview": "{\n \"under_game_score_y\": 200,\n \"press_coefficient\": 1.95,\n \"piece_base_height_1_2\": 13,\n \"piece_body_width\":"
},
{
"path": "config/iPhone/8P_7P_6sP_6P_config.json",
"chars": 222,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.2,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\": "
},
{
"path": "config/iPhone/8_config.json",
"chars": 225,
"preview": "{\n \"under_game_score_y\": 200,\n \"press_coefficient\": 1.97,\n \"piece_base_height_1_2\": 13,\n \"piece_body_width\":"
},
{
"path": "config/iPhone/SE_config.json",
"chars": 225,
"preview": "{\n \"under_game_score_y\": 190,\n \"press_coefficient\": 2.3,\n \"piece_base_height_1_2\": 12,\n \"piece_body_width\": "
},
{
"path": "config/iPhone/X_config.json",
"chars": 223,
"preview": "{\n \"under_game_score_y\": 170,\n \"press_coefficient\": 1.31,\n \"piece_base_height_1_2\": 23,\n \"piece_body_width\":"
},
{
"path": "config/mi/max2_config.json",
"chars": 224,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.5,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\": "
},
{
"path": "config/mi/mi5_config.json",
"chars": 226,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.475,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\""
},
{
"path": "config/mi/mi5s_config.json",
"chars": 226,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.475,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\""
},
{
"path": "config/mi/mi5x_config.json",
"chars": 224,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.45,\n \"piece_base_height_1_2\": 25,\n \"piece_body_width\":"
},
{
"path": "config/mi/mi6_config.json",
"chars": 225,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.45,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\":"
},
{
"path": "config/mi/mix2_config.json",
"chars": 128,
"preview": "{\n \"under_game_score_y\": 420,\n \"press_coefficient\": 1.480, \n \"piece_base_height_1_2\": 25,\n \"piece_body_width"
},
{
"path": "config/mi/note2_config.json",
"chars": 225,
"preview": "{\n \"under_game_score_y\": 300,\n \"press_coefficient\": 1.47,\n \"piece_base_height_1_2\": 25,\n \"piece_body_width\":"
},
{
"path": "config/samsung/s7edge_config.json",
"chars": 124,
"preview": "{\n \"under_game_score_y\": 384,\n \"press_coefficient\": 1,\n \"piece_base_height_1_2\": 95,\n \"piece_body_width\": 10"
},
{
"path": "config/samsung/s8.json",
"chars": 127,
"preview": "{\n \"under_game_score_y\": 460,\n \"press_coefficient\": 1.365,\n \"piece_base_height_1_2\": 70,\n \"piece_body_width\""
},
{
"path": "config/samsung/s8在设置里关闭曲面侧屏",
"chars": 0,
"preview": ""
},
{
"path": "config/smartisan/pro2_config.json",
"chars": 217,
"preview": "{\n \"under_game_score_y\": 411,\n \"press_coefficient\": 1.392,\n \"piece_base_height_1_2\": 20,\n \"piece_body_width\""
},
{
"path": "jump_bot/Makefile",
"chars": 52,
"preview": "run:\n\tmkdir -p jumpbot/data\n\tpython3 jumpbot/bot.py\n"
},
{
"path": "jump_bot/README.rst",
"chars": 2955,
"preview": "##############################################################################\nWechat Jump Bot (iOS)\n###################"
},
{
"path": "jump_bot/jumpbot/algos.py",
"chars": 239,
"preview": "import math\n\ndef get_press_time(piece_x, piece_y, board_x, board_y, time_coeff):\n distance = math.sqrt((board_x -"
},
{
"path": "jump_bot/jumpbot/auto.py",
"chars": 4268,
"preview": "import time\nimport math\nimport random\n\nfrom PIL import Image, ImageDraw\n\nimport settings\nfrom connector import Connector"
},
{
"path": "jump_bot/jumpbot/bot.py",
"chars": 972,
"preview": "import argparse\n\nimport settings\nfrom auto import AutoBot\nfrom manual import ManualBot\n\n\ndef config():\n parser = argp"
},
{
"path": "jump_bot/jumpbot/connector.py",
"chars": 393,
"preview": "import wda\n\nimport settings\n\n\nclass Connector:\n\n\n def __init__(self, image_dir=settings.IMAGE_DIR):\n self.imag"
},
{
"path": "jump_bot/jumpbot/manual.py",
"chars": 2223,
"preview": "import time\nimport math\n\nimport numpy as np\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport matplotlib.anim"
},
{
"path": "jump_bot/jumpbot/settings.py",
"chars": 2475,
"preview": "# Wechat Jump Bot (iOS)\n# ----------------------------------------------------------------------------\n\nimport os\n\nCURRE"
},
{
"path": "jump_bot/requirements.txt",
"chars": 199,
"preview": "backports.functools-lru-cache==1.4\ncycler==0.10.0\nmatplotlib==2.1.1\nnumpy==1.13.3\nolefile==0.44\nopencv-python==3.4.0.12\n"
},
{
"path": "requirements.txt",
"chars": 281,
"preview": "backports.functools-lru-cache==1.4\r\ncycler==0.10.0\r\nmatplotlib==2.1.1\r\nnumpy==1.13.3\r\nolefile==0.44\r\nopencv-python==3.4."
},
{
"path": "resource/model/checkpoint",
"chars": 77,
"preview": "model_checkpoint_path: \"model.ckpt\"\nall_model_checkpoint_paths: \"model.ckpt\"\n"
},
{
"path": "wechat_jump.py",
"chars": 2138,
"preview": "# -*- coding: utf-8 -*-\nfrom __future__ import print_function, division\nimport os\nimport time\nimport datetime\nimport mat"
},
{
"path": "wechat_jump_auto.py",
"chars": 8182,
"preview": "# -*- coding: utf-8 -*-\n\n\"\"\"\n=== 思路 ===\n核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,\n 根据两个点的距离乘以一个时间系数获得长按的时间\n识别棋子:靠棋子的颜色来识别位"
},
{
"path": "wechat_jump_auto_ai.py",
"chars": 13375,
"preview": "# coding: utf-8\n'''\n# === 思路 ===\n# 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,\n# 根据两个点的距离乘以一个时间系数获得长按的时间\n# 识别棋子:靠棋子的颜色来识别位"
},
{
"path": "wechat_jump_auto_curves.py",
"chars": 11662,
"preview": "# -*- coding: utf-8 -*-\n\n\"\"\"\n##基于python3.5(64位)\n###如果缺少scikit-image库,建议进下面网址下载whl直接安装\n##https://www.lfd.uci.edu/~gohlke/"
},
{
"path": "wechat_jump_auto_iOS.py",
"chars": 5919,
"preview": "# -*- coding: utf-8 -*-\n\n\"\"\"\n# === 思路 ===\n# 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,\n# 根据两个点的距离乘以一个时间系数获得长按的时间\n# 识别棋子:靠"
},
{
"path": "wechat_jump_auto_slim.py",
"chars": 6720,
"preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n__author__ = 'Erimus'\n'''\n这个是精简版本,只取x轴距离。\n可以适配任意屏幕。\n把磁盘读写截图改为内存读写。\n可以防止被b"
},
{
"path": "wechat_jump_iOS_py3.py",
"chars": 1668,
"preview": "# -*- coding: utf-8 -*-\nimport time\nimport wda\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib.anim"
},
{
"path": "wechat_jump_py3.py",
"chars": 1579,
"preview": "# -*- coding: utf-8 -*-\nimport os\nimport time\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib.anima"
}
]
// ... and 3 more files (download for full content)
About this extraction
This page contains the full source code of the wangshub/wechat_jump_game GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (99.1 KB), approximately 32.8k tokens, and a symbol index with 104 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.