The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .github
    ├── CONTRIBUTING.md
    ├── ISSUE_TEMPLATE.md
    └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── README.rst
├── Tools
    ├── AdbWinApi.dll
    ├── AdbWinUsbApi.dll
    ├── README.md
    ├── adb.exe
    └── fastboot.exe
├── 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
    ├── image
    │   ├── character.png
    │   ├── jump.gif
    │   └── qrcode_for_gh_3586401957c4_258.jpg
    └── 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


/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/.github/CONTRIBUTING.md


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
 1 | <!--
 2 | 感谢提交问题,提交 issue 前请先通过关键字搜索已经存在或解决了的 issue,避免重复提交相同内容。
 3 | 请说清楚自己遇到了什么问题,不然可能被 close。
 4 | 还没有自己机型配置文件的请自己去创建一个,好用的话欢迎 PR。
 5 | 
 6 | ### 如果代码不能启动,执行的时候直接报错
 7 | 
 8 | 请贴出:
 9 | 1. 代码版本号,代码中的 VERSION 变量
10 | 2. 使用环境(操作系统、Python 版本)
11 | 3. 尽量完整的代码执行命令,和打印的错误信息
12 | 
13 | ### 如果代码能运行,但是达不到想要的效果
14 | 
15 | 请贴出:
16 | 1. 代码版本号,代码中的 VERSION 变量
17 | 2. 使用环境 (运行脚本中已包含自动识别功能,复制即可)
18 | 3. 遇到问题(如有可能,请详细描述,建议附上截图。不负责的 issue 可能会被 close)
19 | 具体实例如下,按照该例进行提交 issue
20 | -->
21 | 
22 | 程序版本号:x.x.x
23 | **********
24 | Screen: Physical size: x
25 | Density: Physical density: x
26 | Override density: x
27 | Device: x
28 | Phone OS: x
29 | Host OS: x
30 | Python: x
31 | **********
32 | 
33 | 遇到问题:
34 | 
35 | 改进和建议:无
36 | 


--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
 1 | <!--
 2 | 感谢您的 pull request!
 3 | 
 4 | ## 在 PR 前请尽量做到:
 5 | - PR 应基于最新的 dev 分支
 6 | ```
 7 |   git remote add wangshub https://github.com/wangshub/wechat_jump_game.git
 8 |   git fetch
 9 |   git rebase wangshub/master
10 | ```
11 | 
12 | - 分支名是有意义的名称,如 add-config-file-for-mi5s 而不是 patch-1
13 | - Base 选择 master 分支
14 | - 尽量遵守 PEP8 规范
15 | - 更新脚本中的 VERSION 字段
16 | - 请描述一下 PR 做的事情,更新算法或配置文件请附上最高分数
17 | - 请明确提交类型,为 PR 标题添加前缀:[类型](类型可填写文档,配置,优化,修复等)
18 | 
19 | -->
20 | 
21 | 本次 PR 主要做的事情:
22 | 
23 | - x
24 | 
25 | 修改后最高分数:x
26 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | # user-defined
  2 | test/
  3 | pic/
  4 | screenshot_backups/
  5 | autojump.png
  6 | autojump_temp.png
  7 | region*.png
  8 | jump_range.csv
  9 | 
 10 | # Byte-compiled / optimized / DLL files
 11 | __pycache__/
 12 | *.py[cod]
 13 | *$py.class
 14 | 
 15 | # C extensions
 16 | *.so
 17 | 
 18 | # Distribution / packaging
 19 | .Python
 20 | .idea
 21 | build/
 22 | develop-eggs/
 23 | dist/
 24 | downloads/
 25 | eggs/
 26 | .eggs/
 27 | lib/
 28 | lib64/
 29 | parts/
 30 | sdist/
 31 | var/
 32 | wheels/
 33 | *.egg-info/
 34 | .installed.cfg
 35 | *.egg
 36 | MANIFEST
 37 | 
 38 | # PyInstaller
 39 | #  Usually these files are written by a python script from a template
 40 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 41 | *.manifest
 42 | *.spec
 43 | 
 44 | # Installer logs
 45 | pip-log.txt
 46 | pip-delete-this-directory.txt
 47 | 
 48 | # Unit test / coverage reports
 49 | htmlcov/
 50 | .tox/
 51 | .coverage
 52 | .coverage.*
 53 | .cache
 54 | nosetests.xml
 55 | coverage.xml
 56 | *.cover
 57 | .hypothesis/
 58 | 
 59 | # Translations
 60 | *.mo
 61 | *.pot
 62 | 
 63 | # Django stuff:
 64 | *.log
 65 | .static_storage/
 66 | .media/
 67 | local_settings.py
 68 | 
 69 | # Flask stuff:
 70 | instance/
 71 | .webassets-cache
 72 | 
 73 | # Scrapy stuff:
 74 | .scrapy
 75 | 
 76 | # Sphinx documentation
 77 | docs/_build/
 78 | 
 79 | # PyBuilder
 80 | target/
 81 | 
 82 | # Jupyter Notebook
 83 | .ipynb_checkpoints
 84 | 
 85 | # pyenv
 86 | .python-version
 87 | 
 88 | # celery beat schedule file
 89 | celerybeat-schedule
 90 | 
 91 | # SageMath parsed files
 92 | *.sage.py
 93 | 
 94 | # Environments
 95 | .env
 96 | .venv
 97 | env/
 98 | venv/
 99 | ENV/
100 | env.bak/
101 | venv.bak/
102 | 
103 | # Spyder project settings
104 | .spyderproject
105 | .spyproject
106 | 
107 | # Rope project settings
108 | .ropeproject
109 | 
110 | # mkdocs documentation
111 | /site
112 | 
113 | # mypy
114 | .mypy_cache/
115 | 
116 | # for mac
117 | .DS_store


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
  1 |                                  Apache License
  2 |                            Version 2.0, January 2004
  3 |                         http://www.apache.org/licenses/
  4 | 
  5 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 | 
  7 |    1. Definitions.
  8 | 
  9 |       "License" shall mean the terms and conditions for use, reproduction,
 10 |       and distribution as defined by Sections 1 through 9 of this document.
 11 | 
 12 |       "Licensor" shall mean the copyright owner or entity authorized by
 13 |       the copyright owner that is granting the License.
 14 | 
 15 |       "Legal Entity" shall mean the union of the acting entity and all
 16 |       other entities that control, are controlled by, or are under common
 17 |       control with that entity. For the purposes of this definition,
 18 |       "control" means (i) the power, direct or indirect, to cause the
 19 |       direction or management of such entity, whether by contract or
 20 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 21 |       outstanding shares, or (iii) beneficial ownership of such entity.
 22 | 
 23 |       "You" (or "Your") shall mean an individual or Legal Entity
 24 |       exercising permissions granted by this License.
 25 | 
 26 |       "Source" form shall mean the preferred form for making modifications,
 27 |       including but not limited to software source code, documentation
 28 |       source, and configuration files.
 29 | 
 30 |       "Object" form shall mean any form resulting from mechanical
 31 |       transformation or translation of a Source form, including but
 32 |       not limited to compiled object code, generated documentation,
 33 |       and conversions to other media types.
 34 | 
 35 |       "Work" shall mean the work of authorship, whether in Source or
 36 |       Object form, made available under the License, as indicated by a
 37 |       copyright notice that is included in or attached to the work
 38 |       (an example is provided in the Appendix below).
 39 | 
 40 |       "Derivative Works" shall mean any work, whether in Source or Object
 41 |       form, that is based on (or derived from) the Work and for which the
 42 |       editorial revisions, annotations, elaborations, or other modifications
 43 |       represent, as a whole, an original work of authorship. For the purposes
 44 |       of this License, Derivative Works shall not include works that remain
 45 |       separable from, or merely link (or bind by name) to the interfaces of,
 46 |       the Work and Derivative Works thereof.
 47 | 
 48 |       "Contribution" shall mean any work of authorship, including
 49 |       the original version of the Work and any modifications or additions
 50 |       to that Work or Derivative Works thereof, that is intentionally
 51 |       submitted to Licensor for inclusion in the Work by the copyright owner
 52 |       or by an individual or Legal Entity authorized to submit on behalf of
 53 |       the copyright owner. For the purposes of this definition, "submitted"
 54 |       means any form of electronic, verbal, or written communication sent
 55 |       to the Licensor or its representatives, including but not limited to
 56 |       communication on electronic mailing lists, source code control systems,
 57 |       and issue tracking systems that are managed by, or on behalf of, the
 58 |       Licensor for the purpose of discussing and improving the Work, but
 59 |       excluding communication that is conspicuously marked or otherwise
 60 |       designated in writing by the copyright owner as "Not a Contribution."
 61 | 
 62 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 63 |       on behalf of whom a Contribution has been received by Licensor and
 64 |       subsequently incorporated within the Work.
 65 | 
 66 |    2. Grant of Copyright License. Subject to the terms and conditions of
 67 |       this License, each Contributor hereby grants to You a perpetual,
 68 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 69 |       copyright license to reproduce, prepare Derivative Works of,
 70 |       publicly display, publicly perform, sublicense, and distribute the
 71 |       Work and such Derivative Works in Source or Object form.
 72 | 
 73 |    3. Grant of Patent License. Subject to the terms and conditions of
 74 |       this License, each Contributor hereby grants to You a perpetual,
 75 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 76 |       (except as stated in this section) patent license to make, have made,
 77 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 78 |       where such license applies only to those patent claims licensable
 79 |       by such Contributor that are necessarily infringed by their
 80 |       Contribution(s) alone or by combination of their Contribution(s)
 81 |       with the Work to which such Contribution(s) was submitted. If You
 82 |       institute patent litigation against any entity (including a
 83 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 84 |       or a Contribution incorporated within the Work constitutes direct
 85 |       or contributory patent infringement, then any patent licenses
 86 |       granted to You under this License for that Work shall terminate
 87 |       as of the date such litigation is filed.
 88 | 
 89 |    4. Redistribution. You may reproduce and distribute copies of the
 90 |       Work or Derivative Works thereof in any medium, with or without
 91 |       modifications, and in Source or Object form, provided that You
 92 |       meet the following conditions:
 93 | 
 94 |       (a) You must give any other recipients of the Work or
 95 |           Derivative Works a copy of this License; and
 96 | 
 97 |       (b) You must cause any modified files to carry prominent notices
 98 |           stating that You changed the files; and
 99 | 
100 |       (c) You must retain, in the Source form of any Derivative Works
101 |           that You distribute, all copyright, patent, trademark, and
102 |           attribution notices from the Source form of the Work,
103 |           excluding those notices that do not pertain to any part of
104 |           the Derivative Works; and
105 | 
106 |       (d) If the Work includes a "NOTICE" text file as part of its
107 |           distribution, then any Derivative Works that You distribute must
108 |           include a readable copy of the attribution notices contained
109 |           within such NOTICE file, excluding those notices that do not
110 |           pertain to any part of the Derivative Works, in at least one
111 |           of the following places: within a NOTICE text file distributed
112 |           as part of the Derivative Works; within the Source form or
113 |           documentation, if provided along with the Derivative Works; or,
114 |           within a display generated by the Derivative Works, if and
115 |           wherever such third-party notices normally appear. The contents
116 |           of the NOTICE file are for informational purposes only and
117 |           do not modify the License. You may add Your own attribution
118 |           notices within Derivative Works that You distribute, alongside
119 |           or as an addendum to the NOTICE text from the Work, provided
120 |           that such additional attribution notices cannot be construed
121 |           as modifying the License.
122 | 
123 |       You may add Your own copyright statement to Your modifications and
124 |       may provide additional or different license terms and conditions
125 |       for use, reproduction, or distribution of Your modifications, or
126 |       for any such Derivative Works as a whole, provided Your use,
127 |       reproduction, and distribution of the Work otherwise complies with
128 |       the conditions stated in this License.
129 | 
130 |    5. Submission of Contributions. Unless You explicitly state otherwise,
131 |       any Contribution intentionally submitted for inclusion in the Work
132 |       by You to the Licensor shall be under the terms and conditions of
133 |       this License, without any additional terms or conditions.
134 |       Notwithstanding the above, nothing herein shall supersede or modify
135 |       the terms of any separate license agreement you may have executed
136 |       with Licensor regarding such Contributions.
137 | 
138 |    6. Trademarks. This License does not grant permission to use the trade
139 |       names, trademarks, service marks, or product names of the Licensor,
140 |       except as required for reasonable and customary use in describing the
141 |       origin of the Work and reproducing the content of the NOTICE file.
142 | 
143 |    7. Disclaimer of Warranty. Unless required by applicable law or
144 |       agreed to in writing, Licensor provides the Work (and each
145 |       Contributor provides its Contributions) on an "AS IS" BASIS,
146 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 |       implied, including, without limitation, any warranties or conditions
148 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 |       PARTICULAR PURPOSE. You are solely responsible for determining the
150 |       appropriateness of using or redistributing the Work and assume any
151 |       risks associated with Your exercise of permissions under this License.
152 | 
153 |    8. Limitation of Liability. In no event and under no legal theory,
154 |       whether in tort (including negligence), contract, or otherwise,
155 |       unless required by applicable law (such as deliberate and grossly
156 |       negligent acts) or agreed to in writing, shall any Contributor be
157 |       liable to You for damages, including any direct, indirect, special,
158 |       incidental, or consequential damages of any character arising as a
159 |       result of this License or out of the use or inability to use the
160 |       Work (including but not limited to damages for loss of goodwill,
161 |       work stoppage, computer failure or malfunction, or any and all
162 |       other commercial damages or losses), even if such Contributor
163 |       has been advised of the possibility of such damages.
164 | 
165 |    9. Accepting Warranty or Additional Liability. While redistributing
166 |       the Work or Derivative Works thereof, You may choose to offer,
167 |       and charge a fee for, acceptance of support, warranty, indemnity,
168 |       or other liability obligations and/or rights consistent with this
169 |       License. However, in accepting such obligations, You may act only
170 |       on Your own behalf and on Your sole responsibility, not on behalf
171 |       of any other Contributor, and only if You agree to indemnify,
172 |       defend, and hold each Contributor harmless for any liability
173 |       incurred by, or claims asserted against, such Contributor by reason
174 |       of your accepting any such warranty or additional liability.
175 | 
176 |    END OF TERMS AND CONDITIONS
177 | 
178 |    APPENDIX: How to apply the Apache License to your work.
179 | 
180 |       To apply the Apache License to your work, attach the following
181 |       boilerplate notice, with the fields enclosed by brackets "[]"
182 |       replaced with your own identifying information. (Don't include
183 |       the brackets!)  The text should be enclosed in the appropriate
184 |       comment syntax for the file format. We also recommend that a
185 |       file or class name and description of purpose be included on the
186 |       same "printed page" as the copyright notice for easier
187 |       identification within third-party archives.
188 | 
189 |    Copyright [wangshub] [name of copyright owner]
190 | 
191 |    Licensed under the Apache License, Version 2.0 (the "License");
192 |    you may not use this file except in compliance with the License.
193 |    You may obtain a copy of the License at
194 | 
195 |        http://www.apache.org/licenses/LICENSE-2.0
196 | 
197 |    Unless required by applicable law or agreed to in writing, software
198 |    distributed under the License is distributed on an "AS IS" BASIS,
199 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 |    See the License for the specific language governing permissions and
201 |    limitations under the License.
202 | 


--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | run:
2 | 	mkdir -p jumpbot/data
3 | 	python3 jumpbot/bot.py
4 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # 教你用 Python 来玩微信跳一跳
 2 | [![GitHub stars](https://img.shields.io/github/stars/wangshub/wechat_jump_game.svg)](https://github.com/wangshub/wechat_jump_game/stargazers) [![GitHub forks](https://img.shields.io/github/forks/wangshub/wechat_jump_game.svg)](https://github.com/wangshub/wechat_jump_game/network) [![GitHub license](https://img.shields.io/github/license/wangshub/wechat_jump_game.svg)](https://github.com/wangshub/wechat_jump_game/blob/master/LICENSE)
 3 | 
 4 | [![Throughput Graph](https://graphs.waffle.io/wangshub/wechat_jump_game/throughput.svg)](https://waffle.io/wangshub/wechat_jump_game/metrics/throughput) 
 5 | 
 6 | ## 游戏模式
 7 | 
 8 | > 2017 年 12 月 28 日下午,微信发布了 6.6.1 版本,加入了「小游戏」功能,并提供了官方 DEMO「跳一跳」。这是一个 2.5D 插画风格的益智游戏,玩家可以通过按压屏幕时间的长短来控制这个「小人」跳跃的距离。分数越高,那么在好友排行榜更加靠前。通过 Python 脚本自动运行,让你轻松霸榜。
 9 | 
10 | ![](./resource/image/jump.gif)
11 | 
12 | 可能刚开始上手的时候,因为时间距离之间的关系把握不恰当,只能跳出几个就掉到了台子下面。**如果能利用图像识别精确测量出起始和目标点之间测距离,就可以估计按压的时间来精确跳跃。**
13 | 
14 | ## 原理说明
15 | 
16 | ##### 由于微信检测非常严厉,这里的防禁代码可能已经不起作用,主要供学习用途
17 | 
18 | 1. 将手机点击到《跳一跳》小程序界面
19 | 
20 | 2. 用 ADB 工具获取当前手机截图,并用 ADB 将截图 pull 上来
21 | ```shell
22 | adb shell screencap -p /sdcard/autojump.png
23 | adb pull /sdcard/autojump.png .
24 | ```
25 | 
26 | 3. 计算按压时间
27 |   * 手动版:用 Matplotlib 显示截图,用鼠标先点击起始点位置,然后点击目标位置,计算像素距离;
28 |   * 自动版:靠棋子的颜色来识别棋子,靠底色和方块的色差来识别棋盘;
29 | 
30 | 4. 用 ADB 工具点击屏幕蓄力一跳
31 | ```shell
32 | adb shell input swipe x y x y time(ms)
33 | ```
34 | 
35 | 
36 | 
37 | ## 使用教程
38 | 
39 | 相关软件工具安装和使用步骤请参考 [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)
40 | 
41 | #### 获取源码
42 | 
43 | ```
44 | - git clone https://github.com/wangshub/wechat_jump_game.git
45 | 
46 | ```
47 | ##### 非常推荐使用Python3,避免编码及import问题
48 | ## PR 要求
49 | ##### 请选择 merge 进 master 分支,并且标题写上简短描述,例子 
50 | [优化] 使用PEP8优化代码
51 | 
52 | ## 版本说明
53 | 
54 | - master 分支:稳定版本,已通过测试
55 | - dev 分支:开发版本,包含一些较稳定的新功能,累计多个功能并测试通过后合并至 prod 分支
56 | - 其他分支:功能开发 (feature) 或问题修复 (bugfix),属于最新尝鲜版本,可能处于开发中的状态,基本完成后合并至 dev 分支
57 | 
58 | ## FAQ
59 | 
60 | - 详见 [Wiki-FAQ](https://github.com/wangshub/wechat_jump_game/wiki/FAQ)
61 | 
62 | ## 更新日志
63 | 
64 | - 详见 [changelog](https://github.com/wangshub/wechat_jump_game/blob/master/changelog.md)
65 | 
66 | ## 开发者列表
67 | 
68 | - 详见 [contributors](https://github.com/wangshub/wechat_jump_game/graphs/contributors)
69 | 
70 | ## 交流
71 | 
72 | - 314659953 (1000 人)
73 | - 176740763 (500 人)
74 | 
75 | - 或者关注我的微信公众号后台留言
76 | 
77 | ![](./resource/image/qrcode_for_gh_3586401957c4_258.jpg)
78 | 
79 | 


--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
  1 | ##############################################################################
  2 | Wechat Jump Bot (iOS)
  3 | ##############################################################################
  4 | 
  5 | ==============================================================================
  6 | Features
  7 | ==============================================================================
  8 | 
  9 | - Auto Mode: play the game automatically;
 10 | - Manual Mode: play the game manually.
 11 | 
 12 | Wechat Jump Game
 13 | 
 14 | .. image:: https://github.com/alpesis-ai/wechat-jumpbot/blob/master/images/auto.png
 15 |    :height: 1334px
 16 |    :width: 500px
 17 | 
 18 | 
 19 | ==============================================================================
 20 | How it runs
 21 | ==============================================================================
 22 | 
 23 | Prerequisites
 24 | 
 25 | - WebDriverAgent
 26 | - libimobiledevice
 27 | - Python 3
 28 | 
 29 | WebDriverAgent
 30 | 
 31 | ::
 32 | 
 33 |     $ git clone https://github.com/facebook/WebDriverAgent && cd WebDriverAgent
 34 |     $ brew install carthage
 35 |     $ ./Scripts/bootstrap.sh
 36 |     # open WebDriverAgent.xcodeproj with Xcode
 37 |     # Xcode:
 38 |     # - code sign (general and build_settings): WebDriverAgentLib/WebDriverAgentRunner
 39 |     # - Product -> Destination -> <your device>
 40 |     # - Product -> Scheme -> WebDriverAgentRunner
 41 |     # - Product -> Test
 42 | 
 43 | libimobiledevice (iproxy)
 44 | 
 45 | ::
 46 | 
 47 |     $ brew install libimobiledevice
 48 |     $ iproxy 8100 8100
 49 |     # browse: http://localhost:8100/status
 50 |     # browse: http://localhost:8100/inspector
 51 | 
 52 | Bot Agent (iOS)
 53 | 
 54 | ::
 55 | 
 56 |     $ git clone https://github.com/alpesis-ai/wechat-jumpbot.git
 57 |     $ cd bot-agent-ios
 58 | 
 59 |     $ pip3 install --pre facebook-wda
 60 |     $ pip3 install -r requirements.txt
 61 | 
 62 |     # make run
 63 |     # - model: [ip, plus, ipx, se]
 64 |     # - mode: [auto, manual]
 65 |     # python3 jumpbot/bot.py --model <device_model> --mode <mode>
 66 |     $ mkdir -p jumpbot/data
 67 |     # iphone 6/7
 68 |     $ python3 jumpbot/bot.py --model ip --mode auto
 69 |     # iphone 6/7 plus
 70 |     $ python3 jumpbot/bot.py --model plus --mode auto
 71 |     # iphone X
 72 |     $ python3 jumpbot/bot.py --model ipx --mode auto
 73 |     # iphone SE
 74 |     $ python3 jumpbot/bot.py --model se --mode auto
 75 | 
 76 | 
 77 | ==============================================================================
 78 | Algorithms
 79 | ==============================================================================
 80 | 
 81 | Manual Mode:
 82 | 
 83 | - click the piece(x, y) and board(x, y) and get the coordinates correspondingly
 84 | - calculating the distance and press time
 85 | 
 86 | ::
 87 | 
 88 |     (coord1[0][0] - coord2[0][0])**2 + (coord2[0][1] - coord2[0][1])**2
 89 |     distance = distance ** 0.5
 90 |     press_time = distance * settings.TIME_COEFF
 91 | 
 92 | Auto Mode:
 93 | 
 94 | - the main idea same as the manual mode, but detecting the piece and the board automatically
 95 |     - find coord_y_start_scan
 96 |     - find piece
 97 |     - find board
 98 | 
 99 | 
100 | ==============================================================================
101 | Developement
102 | ==============================================================================
103 | 
104 | ::
105 | 
106 |     connector ---| 
107 |                  | --> auto / manual  --> bot
108 |     algos     ---|
109 | 


--------------------------------------------------------------------------------
/Tools/AdbWinApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/Tools/AdbWinApi.dll


--------------------------------------------------------------------------------
/Tools/AdbWinUsbApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/Tools/AdbWinUsbApi.dll


--------------------------------------------------------------------------------
/Tools/README.md:
--------------------------------------------------------------------------------
1 | ## 所有实验都在该文件夹下运行即可,已放上测试用的代码。免去配置 adb 的麻烦
2 | 
3 | - 复制 wechat_jump_game 根目录下的 config 文件夹以及 wechat_jump_py3.py 文件到本目录下
4 | - 按住 `shift` + 右键  选择在该文件夹下打开命令窗口(Windows 用户请自行 cmd)
5 | - 打开安卓手机的 usb 调试,并连接电脑,在终端输入 `adb devices` 进行测试,如果有连接设备号则表示成功
6 | - 打开微信小游戏,然后运行代码 `python wechat_jump_py3.py`,点击出现的图形起点和终点,棋子自动跳转
7 | 
8 | **注意:这里使用的是不需要配置的 adb 方式,需要在该文件下操作,至于如何自动跳转,只需改变执行脚本即可,这里只做演示**
9 | 


--------------------------------------------------------------------------------
/Tools/adb.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/Tools/adb.exe


--------------------------------------------------------------------------------
/Tools/fastboot.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/Tools/fastboot.exe


--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
 1 | ## 更新日志
 2 | - 2018-1-8
 3 |   - 发布 release 一键启动 app V1.1.2 版本,针对腾讯 ban 方法进行反 ban 操作,详见 [STOP_jump](https://github.com/wangshub/wechat_jump_game/releases)
 4 | 
 5 | - 2018-1-3 :
 6 |   - 发布 release 一键启动 app,详见 [STOP_jump](https://github.com/wangshub/wechat_jump_game/releases)
 7 | 
 8 | - 2017-12-30 :
 9 |   - 请将安卓手机的 USB 调试模式打开,设置 > 更多设置 > 开发者选项 > USB 调试,如果出现运行脚本后小人不跳的情况,请检查是否有打开 “USB 调试(安全模式)”
10 |   - 根据大家反馈:1080 屏幕距离系数 **1.393**,2K 屏幕为 **1**
11 |   - 添加部分机型配置文件,可直接复制使用
12 | 
13 | - 2017-12-29 :
14 |   - 增加更新自动化运行脚本,感谢 GitHub 上的 [@binderclip](https://github.com/binderclip)
15 | 


--------------------------------------------------------------------------------
/common/UnicodeStreamFilter.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | import sys
 3 | 
 4 | if sys.version_info.major != 3:
 5 |     class UnicodeStreamFilter:
 6 | 
 7 |         def __init__(self, target):
 8 |             self.target = target
 9 |             self.encoding = 'utf-8'
10 |             self.errors = 'replace'
11 |             self.encode_to = self.target.encoding
12 | 
13 | 
14 |     def write(self, s):
15 |         if type(s) == str:
16 |             s = s.decode("utf-8")
17 |         s = s.encode(self.encode_to, self.errors).decode(self.encode_to)
18 |         self.target.write(s)
19 | 
20 | 
21 |     if sys.stdout.encoding == 'cp936':
22 |         sys.stdout = UnicodeStreamFilter(sys.stdout)
23 | else:
24 |     pass
25 | 


--------------------------------------------------------------------------------
/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/common/__init__.py


--------------------------------------------------------------------------------
/common/ai.py:
--------------------------------------------------------------------------------
 1 | # coding: utf-8
 2 | 
 3 | # Copyright (c) 2018 BeiTown
 4 | 
 5 | import os
 6 | import pandas
 7 | from sklearn.linear_model import LinearRegression
 8 | 
 9 | 
10 | def linear_model_main(_distances, _press_times, target_distance):
11 |     regr = LinearRegression()
12 |     regr.fit(_distances, _press_times)
13 |     predict_press_time = regr.predict(target_distance)
14 |     result = {}
15 |     # 截距 b
16 |     result['intercept'] = regr.intercept_
17 |     # 斜率值 k
18 |     result['coefficient'] = regr.coef_
19 |     # 预估的按压时间
20 |     result['value'] = predict_press_time
21 |     return result
22 | 
23 | 
24 | def computing_k_b_v(target_distance):
25 |     result = linear_model_main(distances, press_times, target_distance)
26 |     b = result['intercept']
27 |     k = result['coefficient']
28 |     v = result['value']
29 |     return k[0], b[0], v[0]
30 | 
31 | 
32 | def add_data(distance, press_time):
33 |     distances.append([distance])
34 |     press_times.append([press_time])
35 |     save_data('./jump_range.csv', distances, press_times)
36 | 
37 | 
38 | def save_data(file_name, distances, press_times):
39 |     pf = pandas.DataFrame({'Distance': distances, 'Press_time': press_times})
40 |     # print(pf)
41 |     pf.to_csv(file_name, index=False, sep=',')
42 | 
43 | 
44 | def get_data(file_name):
45 |     data = pandas.read_csv(file_name)
46 |     distance_array = []
47 |     press_time_array = []
48 |     for distance, press_time in zip(data['Distance'], data['Press_time']):
49 |         distance_array.append([float(distance.strip().strip('[]'))])
50 |         press_time_array.append([float(press_time.strip().strip('[]'))])
51 |     return distance_array, press_time_array
52 | 
53 | 
54 | def init():
55 |     global distances, press_times
56 |     distances = []
57 |     press_times = []
58 | 
59 |     if os.path.exists('./jump_range.csv'):
60 |         distances, press_times = get_data('./jump_range.csv')
61 |     else:
62 |         save_data('./jump_range.csv', [], [])
63 |         return 0
64 | 
65 | 
66 | def get_result_len():
67 |     return len(distances)
68 | 


--------------------------------------------------------------------------------
/common/auto_adb.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | import os
 3 | import subprocess
 4 | import platform
 5 | 
 6 | 
 7 | class auto_adb():
 8 |     def __init__(self):
 9 |         try:
10 |             adb_path = 'adb'
11 |             subprocess.Popen([adb_path], stdout=subprocess.PIPE,
12 |                              stderr=subprocess.PIPE)
13 |             self.adb_path = adb_path
14 |         except OSError:
15 |             if platform.system() == 'Windows':
16 |                 adb_path = os.path.join('Tools', "adb", 'adb.exe')
17 |                 try:
18 |                     subprocess.Popen(
19 |                         [adb_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
20 |                     self.adb_path = adb_path
21 |                 except OSError:
22 |                     pass
23 |             else:
24 |                 try:
25 |                     subprocess.Popen(
26 |                         [adb_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
27 |                 except OSError:
28 |                     pass
29 |             print('请安装 ADB 及驱动并配置环境变量')
30 |             print('具体链接: https://github.com/wangshub/wechat_jump_game/wiki')
31 |             exit(1)
32 | 
33 |     def get_screen(self):
34 |         process = os.popen(self.adb_path + ' shell wm size')
35 |         output = process.read()
36 |         return output
37 | 
38 |     def run(self, raw_command):
39 |         command = '{} {}'.format(self.adb_path, raw_command)
40 |         process = os.popen(command)
41 |         output = process.read()
42 |         return output
43 | 
44 |     def test_device(self):
45 |         print('检查设备是否连接...')
46 |         command_list = [self.adb_path, 'devices']
47 |         process = subprocess.Popen(command_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
48 |         output = process.communicate()
49 |         if output[0].decode('utf8') == 'List of devices attached\n\n':
50 |             print('未找到设备')
51 |             print('adb 输出:')
52 |             for each in output:
53 |                 print(each.decode('utf8'))
54 |             exit(1)
55 |         print('设备已连接')
56 |         print('adb 输出:')
57 |         for each in output:
58 |             print(each.decode('utf8'))
59 | 
60 |     def test_density(self):
61 |         process = os.popen(self.adb_path + ' shell wm density')
62 |         output = process.read()
63 |         return output
64 | 
65 |     def test_device_detail(self):
66 |         process = os.popen(self.adb_path + ' shell getprop ro.product.device')
67 |         output = process.read()
68 |         return output
69 | 
70 |     def test_device_os(self):
71 |         process = os.popen(self.adb_path + ' shell getprop ro.build.version.release')
72 |         output = process.read()
73 |         return output
74 | 
75 |     def adb_path(self):
76 |         return self.adb_path
77 | 


--------------------------------------------------------------------------------
/common/config.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | """
 3 | 调取配置文件和屏幕分辨率的代码
 4 | """
 5 | import os
 6 | import sys
 7 | import json
 8 | import re
 9 | 
10 | from common.auto_adb import auto_adb
11 | 
12 | adb = auto_adb()
13 | 
14 | 
15 | def open_accordant_config():
16 |     """
17 |     调用配置文件
18 |     """
19 |     screen_size = _get_screen_size()
20 |     config_file = "{path}/config/{screen_size}/config.json".format(
21 |         path=sys.path[0],
22 |         screen_size=screen_size
23 |     )
24 | 
25 |     # 优先获取执行文件目录的配置文件
26 |     here = sys.path[0]
27 |     for file in os.listdir(here):
28 |         if re.match(r'(.+)\.json', file):
29 |             file_name = os.path.join(here, file)
30 |             with open(file_name, 'r') as f:
31 |                 print("Load config file from {}".format(file_name))
32 |                 return json.load(f)
33 | 
34 |     # 根据分辨率查找配置文件
35 |     if os.path.exists(config_file):
36 |         with open(config_file, 'r') as f:
37 |             print("正在从 {} 加载配置文件".format(config_file))
38 |             return json.load(f)
39 |     else:
40 |         with open('{}/config/default.json'.format(sys.path[0]), 'r') as f:
41 |             print("Load default config")
42 |             return json.load(f)
43 | 
44 | 
45 | def _get_screen_size():
46 |     """
47 |     获取手机屏幕大小
48 |     """
49 |     size_str = adb.get_screen()
50 |     m = re.search(r'(\d+)x(\d+)', size_str)
51 |     if m:
52 |         return "{height}x{width}".format(height=m.group(2), width=m.group(1))
53 |     return "1920x1080"
54 | 


--------------------------------------------------------------------------------
/common/debug.py:
--------------------------------------------------------------------------------
  1 | # -*- coding: utf-8 -*-
  2 | """
  3 | 这是debug的代码,当DEBUG_SWITCH开关开启的时候,会将各种信息存在本地,方便检查故障
  4 | """
  5 | import os
  6 | import sys
  7 | import shutil
  8 | import math
  9 | from PIL import ImageDraw
 10 | import platform
 11 | if platform.system() == 'Windows':
 12 |     os.chdir(os.getcwd().replace('\\common', ''))
 13 |     path_split = "\\"
 14 | else:
 15 |     os.chdir(os.getcwd().replace('/common', ''))
 16 |     path_split = '/'
 17 | # from common import ai
 18 | try:
 19 |     from common.auto_adb import auto_adb
 20 | except ImportError as ex:
 21 |     print(ex)
 22 |     print('请将脚本放在项目根目录中运行')
 23 |     print('请检查项目根目录中的 common 文件夹是否存在')
 24 |     exit(1)
 25 | screenshot_backup_dir = 'screenshot_backups'
 26 | adb = auto_adb()
 27 | 
 28 | 
 29 | def make_debug_dir(screenshot_backup_dir):
 30 |     """
 31 |     创建备份文件夹
 32 |     """
 33 |     if not os.path.isdir(screenshot_backup_dir):
 34 |         os.mkdir(screenshot_backup_dir)
 35 | 
 36 | 
 37 | def backup_screenshot(ts):
 38 |     """
 39 |     为了方便失败的时候 debug
 40 |     """
 41 |     make_debug_dir(screenshot_backup_dir)
 42 |     shutil.copy('{}{}autojump.png'.format(os.getcwd(), path_split),
 43 |                 os.path.join(os.getcwd(), screenshot_backup_dir,
 44 |                              str(ts) + '.png'))
 45 | 
 46 | 
 47 | def save_debug_screenshot(ts, im, piece_x, piece_y, board_x, board_y):
 48 |     """
 49 |     对 debug 图片加上详细的注释
 50 |     
 51 |     """
 52 |     make_debug_dir(screenshot_backup_dir)
 53 |     draw = ImageDraw.Draw(im)
 54 |     draw.line((piece_x, piece_y) + (board_x, board_y), fill=2, width=3)
 55 |     draw.line((piece_x, 0, piece_x, im.size[1]), fill=(255, 0, 0))
 56 |     draw.line((0, piece_y, im.size[0], piece_y), fill=(255, 0, 0))
 57 |     draw.line((board_x, 0, board_x, im.size[1]), fill=(0, 0, 255))
 58 |     draw.line((0, board_y, im.size[0], board_y), fill=(0, 0, 255))
 59 |     draw.ellipse((piece_x - 10, piece_y - 10, piece_x + 10, piece_y + 10), fill=(255, 0, 0))
 60 |     draw.ellipse((board_x - 10, board_y - 10, board_x + 10, board_y + 10), fill=(0, 0, 255))
 61 |     del draw
 62 |     im.save(os.path.join(os.getcwd(), screenshot_backup_dir,
 63 |                          '#' + str(ts) + '.png'))
 64 | 
 65 | 
 66 | def computing_error(last_press_time, target_board_x, target_board_y, last_piece_x, last_piece_y, temp_piece_x,
 67 |                     temp_piece_y):
 68 |     """
 69 |     计算跳跃实际误差
 70 |     """
 71 |     target_distance = math.sqrt(
 72 |         (target_board_x - last_piece_x) ** 2 + (target_board_y - last_piece_y) ** 2)  # 上一轮目标跳跃距离
 73 |     actual_distance = math.sqrt((temp_piece_x - last_piece_x) ** 2 + (temp_piece_y - last_piece_y) ** 2)  # 上一轮实际跳跃距离
 74 |     jump_error_value = math.sqrt((target_board_x - temp_piece_x) ** 2 + (target_board_y - temp_piece_y) ** 2)  # 跳跃误差
 75 | 
 76 |     print(round(target_distance), round(jump_error_value), round(actual_distance), round(last_press_time))
 77 |     ''''# 将结果采集进学习字典
 78 |     if last_piece_x > 0 and last_press_time > 0:
 79 |         ai.add_data(round(actual_distance, 2), round(last_press_time))
 80 |         # print(round(actual_distance), round(last_press_time))'''
 81 | 
 82 | 
 83 | def dump_device_info():
 84 |     """
 85 |     显示设备信息
 86 |     """
 87 |     size_str = adb.get_screen()
 88 |     device_str = adb.test_device_detail()
 89 |     phone_os_str = adb.test_device_os()
 90 |     density_str = adb.test_density()
 91 |     print("""**********
 92 | Screen: {size}
 93 | Density: {dpi}
 94 | Device: {device}
 95 | Phone OS: {phone_os}
 96 | Host OS: {host_os}
 97 | Python: {python}
 98 | **********""".format(
 99 |         size=size_str.replace('\n', ''),
100 |         dpi=density_str.replace('\n', ''),
101 |         device=device_str.replace('\n', ''),
102 |         phone_os=phone_os_str.replace('\n', ''),
103 |         host_os=sys.platform,
104 |         python=sys.version
105 |     ))
106 | 


--------------------------------------------------------------------------------
/common/screenshot.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | """
 3 | 手机屏幕截图的代码
 4 | """
 5 | import subprocess
 6 | import os
 7 | import sys
 8 | from PIL import Image
 9 | from io import StringIO
10 | 
11 | try:
12 |     from common.auto_adb import auto_adb
13 | except Exception as ex:
14 |     print(ex)
15 |     print('请将脚本放在项目根目录中运行')
16 |     print('请检查项目根目录中的 common 文件夹是否存在')
17 |     exit(1)
18 | adb = auto_adb()
19 | # SCREENSHOT_WAY 是截图方法,经过 check_screenshot 后,会自动递减,不需手动修改
20 | SCREENSHOT_WAY = 3
21 | 
22 | 
23 | def pull_screenshot():
24 |     """
25 |     获取屏幕截图,目前有 0 1 2 3 四种方法,未来添加新的平台监测方法时,
26 |     可根据效率及适用性由高到低排序
27 |     """
28 |     global SCREENSHOT_WAY
29 |     if 1 <= SCREENSHOT_WAY <= 3:
30 |         process = subprocess.Popen(
31 |             adb.adb_path + ' shell screencap -p',
32 |             shell=True, stdout=subprocess.PIPE)
33 |         binary_screenshot = process.stdout.read()
34 |         if SCREENSHOT_WAY == 2:
35 |             binary_screenshot = binary_screenshot.replace(b'\r\n', b'\n')
36 |         elif SCREENSHOT_WAY == 1:
37 |             binary_screenshot = binary_screenshot.replace(b'\r\r\n', b'\n')
38 |         return Image.open(StringIO(binary_screenshot))
39 |     elif SCREENSHOT_WAY == 0:
40 |         adb.run('shell screencap -p /sdcard/autojump.png')
41 |         adb.run('pull /sdcard/autojump.png .')
42 |         return Image.open('./autojump.png')
43 | 
44 | 
45 | def check_screenshot():
46 |     """
47 |     检查获取截图的方式
48 |     """
49 |     global SCREENSHOT_WAY
50 |     if os.path.isfile('autojump.png'):
51 |         try:
52 |             os.remove('autojump.png')
53 |         except Exception:
54 |             pass
55 |     if SCREENSHOT_WAY < 0:
56 |         print('暂不支持当前设备')
57 |         sys.exit()
58 |     try:
59 |         im = pull_screenshot()
60 |         im.load()
61 |         im.close()
62 |         print('采用方式 {} 获取截图'.format(SCREENSHOT_WAY))
63 |     except Exception:
64 |         SCREENSHOT_WAY -= 1
65 |         check_screenshot()
66 | 


--------------------------------------------------------------------------------
/config/1280x720/config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 200,
 3 |     "press_coefficient": 2.099,
 4 |     "piece_base_height_1_2": 13,
 5 |     "piece_body_width": 47,
 6 |     "swipe": {
 7 |         "x1": 374,
 8 |         "y1": 1060,
 9 |         "x2": 374,
10 |         "y2": 1060
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/1440x720/config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 200,
 3 |     "press_coefficient": 2.099,
 4 |     "piece_base_height_1_2": 13,
 5 |     "piece_body_width": 47,
 6 |     "swipe" : {
 7 |       "x1": 360,
 8 |       "y1": 1140,
 9 |       "x2": 360,
10 |       "y2": 1142
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/1800x1080/config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.2,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "swipe": {
 7 |         "x1": 500,
 8 |         "y1": 1600,
 9 |         "x2": 500,
10 |         "y2": 1602
11 |     }
12 | }


--------------------------------------------------------------------------------
/config/1920x1080/config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.392,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "head_diameter": 60,
 7 |     "swipe": {
 8 |         "x1": 500,
 9 |         "y1": 1600,
10 |         "x2": 500,
11 |         "y2": 1602
12 |     }
13 | }
14 | 


--------------------------------------------------------------------------------
/config/2160x1080/config.json:
--------------------------------------------------------------------------------
1 | {
2 |     "under_game_score_y": 420,
3 |     "press_coefficient": 1.372, 
4 |     "piece_base_height_1_2": 25,
5 |     "piece_body_width": 85
6 | }
7 | 


--------------------------------------------------------------------------------
/config/2560x1440/config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 410,
 3 |     "press_coefficient": 1.475,
 4 |     "piece_base_height_1_2": 28,
 5 |     "piece_body_width": 110,
 6 |     "swipe": {
 7 |         "x1": 320,
 8 |         "y1": 410,
 9 |         "x2": 320,
10 |         "y2": 410
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/720x1280:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 369,
 3 |     "press_coefficient":  2.003,
 4 |     "piece_base_height_1_2": 13,
 5 |     "piece_body_width": 45,
 6 |     "swipe" : {
 7 |       "x1": 374,
 8 |       "y1": 1060,
 9 |       "x2": 374,
10 |       "y2": 1060
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/960x540/config.json:
--------------------------------------------------------------------------------
1 | {
2 |     "under_game_score_y": 300, 
3 |     "press_coefficient": 2.732, 
4 |     "piece_base_height_1_2": 20, 
5 |     "piece_body_width": 70
6 | }
7 | 


--------------------------------------------------------------------------------
/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 |     "under_game_score_y": 300, 
3 |     "press_coefficient": 1.392, 
4 |     "piece_base_height_1_2": 20, 
5 |     "piece_body_width": 70
6 | }
7 | 


--------------------------------------------------------------------------------
/config/huawei/honorV8:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 400,
 3 |     "press_coefficient": 1.07,
 4 |     "piece_base_height_1_2": 90,
 5 |     "piece_body_width": 120,
 6 |     "swipe": {
 7 |         "x1": 730,
 8 |         "y1": 2100,
 9 |         "x2": 720,
10 |         "y2": 2100
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/huawei/honor_note8_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 400,
 3 |     "press_coefficient": 1.04,
 4 |     "piece_base_height_1_2": 90,
 5 |     "piece_body_width": 120,
 6 |     "swipe": {
 7 |         "x1": 730,
 8 |         "y1": 2100,
 9 |         "x2": 720,
10 |         "y2": 2100
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/iPhone/6_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 200,
 3 |     "press_coefficient": 1.95,
 4 |     "piece_base_height_1_2": 13,
 5 |     "piece_body_width": 49,
 6 |     "swipe": {
 7 |         "x1": 375,
 8 |         "y1": 1055,
 9 |         "x2": 375,
10 |         "y2": 1055
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/iPhone/8P_7P_6sP_6P_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.2,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "swipe": {
 7 |         "x1": 320,
 8 |         "y1": 410,
 9 |         "x2": 320,
10 |         "y2": 410
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/iPhone/8_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 200,
 3 |     "press_coefficient": 1.97,
 4 |     "piece_base_height_1_2": 13,
 5 |     "piece_body_width": 50,
 6 |     "swipe": {
 7 |         "x1": 375,
 8 |         "y1": 1200,
 9 |         "x2": 375,
10 |         "y2": 1200
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/iPhone/SE_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 190,
 3 |     "press_coefficient": 2.3,
 4 |     "piece_base_height_1_2": 12,
 5 |     "piece_body_width": 50,
 6 | 
 7 |     "swipe" : {
 8 |         "x1": 375,
 9 |         "y1": 1055,
10 |         "x2": 375,
11 |         "y2": 1055
12 |     }
13 | }


--------------------------------------------------------------------------------
/config/iPhone/X_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 170,
 3 |     "press_coefficient": 1.31,
 4 |     "piece_base_height_1_2": 23,
 5 |     "piece_body_width": 70,
 6 |     "swipe": {
 7 |         "x1": 320,
 8 |         "y1": 410,
 9 |         "x2": 320,
10 |         "y2": 410
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/mi/max2_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.5,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "swipe": {
 7 |         "x1": 500,
 8 |         "y1": 1600,
 9 |         "x2": 500,
10 |         "y2": 1600
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/mi/mi5_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.475,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "swipe": {
 7 |         "x1": 540,
 8 |         "y1": 1514,
 9 |         "x2": 540,
10 |         "y2": 1514
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/mi/mi5s_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.475,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "swipe": {
 7 |         "x1": 540,
 8 |         "y1": 1514,
 9 |         "x2": 540,
10 |         "y2": 1514
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/mi/mi5x_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.45,
 4 |     "piece_base_height_1_2": 25,
 5 |     "piece_body_width": 80,
 6 |     "swipe": {
 7 |         "x1": 560,
 8 |         "y1": 1550,
 9 |         "x2": 560,
10 |         "y2": 1550
11 |     }
12 | }


--------------------------------------------------------------------------------
/config/mi/mi6_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.45,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "swipe": {
 7 |         "x1": 500,
 8 |         "y1": 1600,
 9 |         "x2": 500,
10 |         "y2": 1600
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/mi/mix2_config.json:
--------------------------------------------------------------------------------
1 | {
2 |     "under_game_score_y": 420,
3 |     "press_coefficient": 1.480, 
4 |     "piece_base_height_1_2": 25,
5 |     "piece_body_width": 85
6 | }
7 | 


--------------------------------------------------------------------------------
/config/mi/note2_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 300,
 3 |     "press_coefficient": 1.47,
 4 |     "piece_base_height_1_2": 25,
 5 |     "piece_body_width": 80,
 6 |     "swipe": {
 7 |         "x1": 540,
 8 |         "y1": 1600,
 9 |         "x2": 540,
10 |         "y2": 1600
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/config/samsung/s7edge_config.json:
--------------------------------------------------------------------------------
1 | {
2 |     "under_game_score_y": 384,
3 |     "press_coefficient": 1,
4 |     "piece_base_height_1_2": 95,
5 |     "piece_body_width": 102
6 | }
7 | 


--------------------------------------------------------------------------------
/config/samsung/s8.json:
--------------------------------------------------------------------------------
1 | {
2 |     "under_game_score_y": 460,
3 |     "press_coefficient": 1.365,
4 |     "piece_base_height_1_2": 70,
5 |     "piece_body_width": 75
6 | }
7 | 


--------------------------------------------------------------------------------
/config/samsung/s8在设置里关闭曲面侧屏:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/config/samsung/s8在设置里关闭曲面侧屏


--------------------------------------------------------------------------------
/config/smartisan/pro2_config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "under_game_score_y": 411,
 3 |     "press_coefficient": 1.392,
 4 |     "piece_base_height_1_2": 20,
 5 |     "piece_body_width": 70,
 6 |     "swipe" : {
 7 |       "x1": 320,
 8 |       "y1": 410,
 9 |       "x2": 320,
10 |       "y2": 410
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/jump_bot/Makefile:
--------------------------------------------------------------------------------
1 | run:
2 | 	mkdir -p jumpbot/data
3 | 	python3 jumpbot/bot.py
4 | 


--------------------------------------------------------------------------------
/jump_bot/README.rst:
--------------------------------------------------------------------------------
  1 | ##############################################################################
  2 | Wechat Jump Bot (iOS)
  3 | ##############################################################################
  4 | 
  5 | ==============================================================================
  6 | Features
  7 | ==============================================================================
  8 | 
  9 | - Auto Mode: playing the game automatically;
 10 | - Manual Mode: playing the game by manual.
 11 | 
 12 | Wechat Jump Game
 13 | 
 14 | .. image:: https://github.com/alpesis-ai/wechat-jumpbot/blob/master/images/auto.png
 15 |    :height: 1334px
 16 |    :width: 500px
 17 | 
 18 | 
 19 | ==============================================================================
 20 | How it runs
 21 | ==============================================================================
 22 | 
 23 | Prequsites
 24 | 
 25 | - WebDriverAgent
 26 | - libimobiledevice
 27 | - Python 3
 28 | 
 29 | WebDriverAgent
 30 | 
 31 | ::
 32 | 
 33 |     $ git clone https://github.com/facebook/WebDriverAgent && cd WebDriverAgent
 34 |     $ brew install carthage
 35 |     $ ./Scripts/bootstrap.sh
 36 |     # open WebDriverAgent.xcodeproj with Xcode
 37 |     # Xcode:
 38 |     # - code sign (general and build_settings): WebDriverAgentLib/WebDriverAgentRunner
 39 |     # - Product -> Destination -> <your device>
 40 |     # - Product -> Scheme -> WebDriverAgentRunner
 41 |     # - Product -> Test
 42 | 
 43 | libimobiledevice (iproxy)
 44 | 
 45 | ::
 46 | 
 47 |     $ brew install libimobiledevice
 48 |     $ iproxy 8100 8100
 49 |     # browse: http://localhost:8100/status
 50 |     # browse: http://localhost:8100/inspector
 51 | 
 52 | Bot Agent (iOS)
 53 | 
 54 | ::
 55 | 
 56 |     $ git clone https://github.com/alpesis-ai/wechat-jumpbot.git
 57 |     $ cd bot-agent-ios
 58 | 
 59 |     $ pip3 install --pre facebook-wda
 60 |     $ pip3 install -r requirements.txt
 61 | 
 62 |     # make run
 63 |     # - model: [ip, plus, ipx, se]
 64 |     # - mode: [auto, manual]
 65 |     # python3 jumpbot/bot.py --model <device_model> --mode <mode>
 66 |     $ mkdir -p jumpbot/data
 67 |     # iphone 6/7
 68 |     $ python3 jumpbot/bot.py --model ip --mode auto
 69 |     # iphone 6/7 plus
 70 |     $ python3 jumpbot/bot.py --model plus --mode auto
 71 |     # iphone X
 72 |     $ python3 jumpbot/bot.py --model ipx --mode auto
 73 |     # iphone SE
 74 |     $ python3 jumpbot/bot.py --model se --mode auto
 75 | 
 76 | 
 77 | ==============================================================================
 78 | Algorithms
 79 | ==============================================================================
 80 | 
 81 | Manual Mode:
 82 | 
 83 | - click the piece(x, y) and board(x, y) and get the coordinates correspondingly
 84 | - calculating the distance and press time
 85 | 
 86 | ::
 87 | 
 88 |     (coord1[0][0] - coord2[0][0])**2 + (coord2[0][1] - coord2[0][1])**2
 89 |     distance = distance ** 0.5
 90 |     press_time = distance * settings.TIME_COEFF
 91 | 
 92 | Auto Mode:
 93 | 
 94 | - the main idea same as the manual mode, but detecting the piece and the board automatically
 95 |     - find coord_y_start_scan
 96 |     - find piece
 97 |     - find board
 98 | 
 99 | 
100 | ==============================================================================
101 | Developement
102 | ==============================================================================
103 | 
104 | ::
105 | 
106 |     connector ---| 
107 |                  | --> auto / manual  --> bot
108 |     algos     ---|
109 | 


--------------------------------------------------------------------------------
/jump_bot/jumpbot/algos.py:
--------------------------------------------------------------------------------
1 | import math
2 | 
3 | def get_press_time(piece_x, piece_y, board_x, board_y, time_coeff):
4 |         distance = math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2)
5 |         press_time = distance * time_coeff / 1000
6 |         return press_time
7 | 


--------------------------------------------------------------------------------
/jump_bot/jumpbot/auto.py:
--------------------------------------------------------------------------------
  1 | import time
  2 | import math
  3 | import random
  4 | 
  5 | from PIL import Image, ImageDraw
  6 | 
  7 | import settings
  8 | from connector import Connector
  9 | from algos import get_press_time
 10 | 
 11 | 
 12 | class AutoBot(Connector):
 13 | 
 14 |     def __init__(self, params=settings.get_bot_params()):
 15 |         # init connector
 16 |         super(AutoBot, self).__init__()
 17 | 
 18 |         # init bot
 19 |         self.status = True
 20 | 
 21 |         # init game
 22 |         self.params = params
 23 |         self.swipe_x1 = 0;
 24 |         self.swipe_y1 = 0;
 25 |         self.swipe_x2 = 0;
 26 |         self.swipe_y2 = 0;
 27 | 
 28 |     def run(self):
 29 |         steps = 0
 30 | 
 31 |         while (self.status):
 32 |             self.connector_screenshot()
 33 |             image = Image.open(self.image_dir)
 34 | 
 35 |             steps += 1
 36 |             coord_y_start_scan = self._get_coord_y_start_scan(image)
 37 |             piece_x, piece_y = self._find_piece(image, coord_y_start_scan)
 38 |             board_x, board_y = self._find_board(image, piece_x, piece_y)
 39 |             print("step: ", steps)
 40 |             print("- image: ", image.size)
 41 |             print("- coord_y_start_scan: ", coord_y_start_scan)
 42 |             print("- piece (x, y): ", piece_x, piece_y)
 43 |             print("- board (x, y): ", board_x, board_y)
 44 |             if piece_x == 0: 
 45 |                 print("Game Over.")
 46 |                 return
 47 | 
 48 |             self._set_button_coords(image)           
 49 |             press_time = get_press_time(piece_x, piece_y, board_x, board_y, self.params["TIME_COEFF"])
 50 |             print("- press time: ", press_time)
 51 |             self.connector_taphold(press_time)
 52 |             time.sleep(random.uniform(1, 1.1))
 53 | 
 54 | 
 55 |     def _get_coord_y_start_scan(self, image):
 56 |         width, height = image.size
 57 |         pixels = image.load()
 58 |         coord_y_start_scan = 0
 59 | 
 60 |         for i in range(self.params["COORD_Y_START_SCAN"], height, 50):
 61 |             last_pixel = pixels[0, i]
 62 |             for j in range(1, width):
 63 |                 pixel = pixels[j, i]
 64 | 
 65 |                 if (pixel[0] != last_pixel[0]) or (pixel[1] != last_pixel[1]) or (pixel[2] != last_pixel[2]):
 66 |                     coord_y_start_scan = i - 50
 67 |                     break;
 68 | 
 69 |             if coord_y_start_scan: break;
 70 | 
 71 |         return coord_y_start_scan
 72 | 
 73 |     def _find_piece(self, image, coord_y_start_scan):
 74 |         width, height = image.size
 75 |         pixels = image.load()
 76 | 
 77 |         border_x_scan = int(width/8)
 78 |         piece_x_sum = 0
 79 |         piece_x_counter = 0
 80 |         piece_y_max = 0
 81 | 
 82 |         for i in range(coord_y_start_scan, int(height * 2 / 3)):
 83 |             for j in range(border_x_scan, width - border_x_scan):
 84 |                 pixel = pixels[j, i]
 85 |                 if (50 < pixel[0] < 60) and (53 < pixel[1] < 63) and (95 < pixel[2] < 110):
 86 |                     piece_x_sum += j
 87 |                     piece_x_counter += 1
 88 |                     piece_y_max = max(i, piece_y_max)
 89 | 
 90 |         if not all((piece_x_sum, piece_x_counter)): return 0, 0
 91 |         piece_x = piece_x_sum / piece_x_counter
 92 |         piece_y = piece_y_max - self.params["PIECE_BASE_HEIGHT_HALF"]
 93 |         return piece_x, piece_y
 94 | 
 95 | 
 96 |     def _find_board(self, image, piece_x, piece_y):
 97 |         width, height = image.size
 98 |         pixels = image.load()
 99 | 
100 |         board_x = 0
101 |         board_y = 0
102 | 
103 |         for i in range(int(height/3), int(height * 2/3)):
104 |             if board_x or board_y: 
105 |                 break;
106 | 
107 |             board_x_sum = 0
108 |             board_x_counter = 0
109 |             last_pixel = pixels[0, i]
110 |             for j in range(width):
111 |                 pixel = pixels[j, i]
112 |                 if (abs(j - piece_x) < self.params["PIECE_BODY_WIDTH"]):
113 |                     continue
114 |                 if abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) > 10:
115 |                     board_x_sum += j
116 |                     board_x_counter += 1
117 | 
118 |             if board_x_sum:
119 |                 board_x = board_x_sum / board_x_counter
120 | 
121 |         # find the centroid of next board
122 |         board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3
123 |         if not all ((board_x, board_y)):   
124 |             return 0, 0
125 | 
126 |         return board_x, board_y
127 | 
128 |     def _set_button_coords(self, image):
129 |         width, height = image.size
130 |         left = width / 2
131 |         top = 1003 * (height / 1280.0) + 10
132 |         self.swipe_x1, self.swipe_y1, self.swipe_x2, self.swipe_y2 = left, top, left, top
133 | 


--------------------------------------------------------------------------------
/jump_bot/jumpbot/bot.py:
--------------------------------------------------------------------------------
 1 | import argparse
 2 | 
 3 | import settings
 4 | from auto import AutoBot
 5 | from manual import ManualBot
 6 | 
 7 | 
 8 | def config():
 9 |     parser = argparse.ArgumentParser(description='Wechat Jump Bot')
10 |     parser.add_argument('-ml', '--model', 
11 |                         type=str,
12 |                         help='device model = [ip, plus, ipx, se]',
13 |                         required=True)
14 | 
15 |     parser.add_argument('-me', '--mode', 
16 |                         type=str,
17 |                         help='mode = [auto, manual]',
18 |                         required=True)
19 | 
20 |     return parser.parse_args()
21 | 
22 | 
23 | def jumpbot(parser):
24 |  
25 |     if parser.mode == "manual":
26 |         bot = ManualBot(params=settings.get_bot_params(parser.model))
27 |         bot.run()
28 | 
29 |     elif parser.mode == "auto":
30 |         bot = AutoBot(params=settings.get_bot_params(parser.model)) 
31 |         bot.run()
32 | 
33 |     else:
34 |         print("ParamError: MODE should be ['auto', 'manual'].")
35 | 
36 | 
37 | if __name__ == '__main__':
38 | 
39 |     parser = config()
40 |     jumpbot(parser)    
41 | 


--------------------------------------------------------------------------------
/jump_bot/jumpbot/connector.py:
--------------------------------------------------------------------------------
 1 | import wda
 2 | 
 3 | import settings
 4 | 
 5 | 
 6 | class Connector:
 7 | 
 8 | 
 9 |     def __init__(self, image_dir=settings.IMAGE_DIR):
10 |         self.image_dir = image_dir
11 | 
12 |         self.client = wda.Client()
13 |         self.session = self.client.session()
14 | 
15 | 
16 |     def connector_screenshot(self):
17 |         self.client.screenshot(self.image_dir)
18 | 
19 | 
20 |     def connector_taphold(self, value):
21 |         self.session.tap_hold(200, 200, value)
22 | 


--------------------------------------------------------------------------------
/jump_bot/jumpbot/manual.py:
--------------------------------------------------------------------------------
 1 | import time
 2 | import math
 3 | 
 4 | import numpy as np
 5 | from PIL import Image
 6 | import matplotlib.pyplot as plt
 7 | import matplotlib.animation as animation
 8 | 
 9 | import settings
10 | from connector import Connector
11 | from algos import get_press_time
12 | 
13 | 
14 | class ManualBot(Connector):
15 | 
16 |     def __init__(self, params=settings.get_bot_params()):
17 |         # init connector
18 |         super(ManualBot, self).__init__()
19 | 
20 |         # init figure
21 |         self.figure = plt.figure()
22 | 
23 |         # actions
24 |         self.steps = 0
25 |         self.params = params
26 |         self.coords = []
27 |         self.ix = [0, 0]
28 |         self.iy = [0, 0]
29 |         self.click_counter = 0
30 |         self.status = True
31 | 
32 | 
33 |     def run(self):
34 |         self.connector_screenshot()
35 |         self.image = plt.imshow(self._read_image(), animated=True)
36 |         self.action()
37 | 
38 |     def action(self):
39 |         self.figure.canvas.mpl_connect('button_press_event', self._onclick)
40 |         ani = animation.FuncAnimation(self.figure, self._update_figure, interval=50, blit=True)
41 |         plt.show()
42 | 
43 | 
44 |     def _onclick(self, event):
45 |         coord = []
46 |         self.ix, self.iy = event.xdata, event.ydata
47 |         coord.append((self.ix, self.iy))
48 |         print("coordinate = ", coord)
49 |         self.coords.append(coord)
50 |         self.click_counter += 1
51 | 
52 |         if self.click_counter > 1:
53 |             self.click_counter = 0
54 |             # next screen
55 |             coord1 = self.coords.pop()
56 |             coord2 = self.coords.pop()
57 |             press_time = get_press_time(coord1[0][0], coord1[0][1],
58 |                                         coord2[0][0], coord2[0][1], 
59 |                                         self.params["TIME_COEFF"])
60 |             self.steps += 1
61 |             print("Step: ", self.steps)
62 |             print("- coord1: ", coord1)
63 |             print("- coord2: ", coord2)
64 |             print("- press_time: ", press_time)
65 |             self.connector_taphold(press_time)
66 |             self.status = True
67 | 
68 | 
69 |     def _update_figure(self, *args):
70 |         if self.status:
71 |             time.sleep(1)
72 |             self.connector_screenshot()
73 |             self.image.set_array(self._read_image())
74 |             self.status = False
75 |         return self.image,
76 | 
77 | 
78 |     def _read_image(self):
79 |         return np.array(Image.open(self.image_dir))
80 | 


--------------------------------------------------------------------------------
/jump_bot/jumpbot/settings.py:
--------------------------------------------------------------------------------
 1 | # Wechat Jump Bot (iOS)
 2 | # ----------------------------------------------------------------------------
 3 | 
 4 | import os
 5 | 
 6 | CURRENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 7 | PROJECT_ROOT = os.path.dirname(CURRENT_DIR)
 8 | PROJECT_DIR = "jumpbot/"
 9 | 
10 | # ----------------------------------------------------------------------------
11 | # Screenshot
12 | 
13 | DATA_DIR = "data/"
14 | IMAGE = "screen.png"
15 | IMAGE_DIR = PROJECT_DIR + DATA_DIR + IMAGE
16 | 
17 | # ----------------------------------------------------------------------------
18 | 
19 | # mode: ['auto', 'manual']
20 | MODE = "manual"
21 | 
22 | # ----------------------------------------------------------------------------
23 | # Params
24 | 
25 | def get_bot_params(model="ip"):
26 | 
27 |     bot_params = {
28 |         "TIME_COEFF": 2.,
29 |         "COORD_Y_START_SCAN": 200,
30 |         "PIECE_BASE_HEIGHT_HALF": 13,
31 |         "PIECE_BODY_WIDTH": 49,
32 |         "SWIPE_X1": 375,
33 |         "SWIPE_Y1": 1055,
34 |         "SWIPE_X2": 375,
35 |         "SWIPE_Y2": 1055
36 |     }
37 | 
38 |     if model == "ip":
39 |         bot_params["TIME_COEFF"] = 2.
40 |         bot_params["COORD_Y_START_SCAN"] = 200
41 |         bot_params["PIECE_BASE_HEIGHT_HALF"] = 13
42 |         bot_params["PIECE_BODY_WIDTH"] = 49
43 |         bot_params["SWIPE_X1"] = 375
44 |         bot_params["SWIPE_Y1"] = 1055
45 |         bot_params["SWIPE_X2"] = 375
46 |         bot_params["SWIPE_Y2"] = 1055
47 | 
48 |     elif model == "plus":
49 |         bot_params["TIME_COEFF"] = 1.2
50 |         bot_params["COORD_Y_START_SCAN"] = 300
51 |         bot_params["PIECE_BASE_HEIGHT_HALF"] = 20
52 |         bot_params["PIECE_BODY_WIDTH"] = 70
53 |         bot_params["SWIPE_X1"] = 320
54 |         bot_params["SWIPE_Y1"] = 410
55 |         bot_params["SWIPE_X2"] = 320
56 |         bot_params["SWIPE_Y2"] = 410
57 | 
58 |     elif model == "ipx":
59 |         bot_params["TIME_COEFF"] = 1.31
60 |         bot_params["COORD_Y_START_SCAN"] = 170
61 |         bot_params["PIECE_BASE_HEIGHT_HALF"] = 23
62 |         bot_params["PIECE_BODY_WIDTH"] = 70
63 |         bot_params["SWIPE_X1"] = 320
64 |         bot_params["SWIPE_Y1"] = 410
65 |         bot_params["SWIPE_X2"] = 320
66 |         bot_params["SWIPE_Y2"] = 410
67 | 
68 |     elif model == "se":
69 |         bot_params["TIME_COEFF"] = 2.3
70 |         bot_params["COORD_Y_START_SCAN"] = 190
71 |         bot_params["PIECE_BASE_HEIGHT_HALF"] = 12
72 |         bot_params["PIECE_BODY_WIDTH"] = 50
73 |         bot_params["SWIPE_X1"] = 375
74 |         bot_params["SWIPE_Y1"] = 1055
75 |         bot_params["SWIPE_X2"] = 375
76 |         bot_params["SWIPE_Y2"] = 1055
77 | 
78 |     else:
79 |         print("ParamError: Unknown model type, model should be [ip, plus, ipx, se]")
80 | 
81 |     return bot_params
82 | 


--------------------------------------------------------------------------------
/jump_bot/requirements.txt:
--------------------------------------------------------------------------------
 1 | backports.functools-lru-cache==1.4
 2 | cycler==0.10.0
 3 | matplotlib==2.1.1
 4 | numpy==1.13.3
 5 | olefile==0.44
 6 | opencv-python==3.4.0.12
 7 | Pillow==4.3.0
 8 | pyparsing==2.2.0
 9 | python-dateutil==2.6.1
10 | pytz==2017.3
11 | six==1.11.0
12 | 


--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
 1 | backports.functools-lru-cache==1.4
 2 | cycler==0.10.0
 3 | matplotlib==2.1.1
 4 | numpy==1.13.3
 5 | olefile==0.44
 6 | opencv-python==3.4.0.12
 7 | Pillow==4.3.0
 8 | pyparsing==2.2.0
 9 | python-dateutil==2.6.1
10 | pytz==2017.3
11 | six==1.11.0
12 | tensorflow==1.4.0
13 | pandas==0.22.0
14 | scipy==1.0.0
15 | scikit_learn==0.19.1
16 | 


--------------------------------------------------------------------------------
/resource/image/character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/resource/image/character.png


--------------------------------------------------------------------------------
/resource/image/jump.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/resource/image/jump.gif


--------------------------------------------------------------------------------
/resource/image/qrcode_for_gh_3586401957c4_258.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/resource/image/qrcode_for_gh_3586401957c4_258.jpg


--------------------------------------------------------------------------------
/resource/model/checkpoint:
--------------------------------------------------------------------------------
1 | model_checkpoint_path: "model.ckpt"
2 | all_model_checkpoint_paths: "model.ckpt"
3 | 


--------------------------------------------------------------------------------
/resource/model/model.ckpt.data-00000-of-00001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/resource/model/model.ckpt.data-00000-of-00001


--------------------------------------------------------------------------------
/resource/model/model.ckpt.index:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/resource/model/model.ckpt.index


--------------------------------------------------------------------------------
/resource/model/model.ckpt.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshub/wechat_jump_game/3cbbb07957901821542fcfc231a09b4f3c9318c9/resource/model/model.ckpt.meta


--------------------------------------------------------------------------------
/wechat_jump.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | from __future__ import print_function, division
 3 | import os
 4 | import time
 5 | import datetime
 6 | import matplotlib.pyplot as plt
 7 | import matplotlib.animation as animation
 8 | import cv2
 9 | 
10 | VERSION = "1.1.4"
11 | scale = 0.25
12 | 
13 | template = cv2.imread('./resource/image/character.png')
14 | template = cv2.resize(template, (0, 0), fx=scale, fy=scale)
15 | template_size = template.shape[:2]
16 | 
17 | 
18 | def search(img):
19 |     result = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
20 |     min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
21 | 
22 |     cv2.rectangle(
23 |         img,
24 |         (min_loc[0], min_loc[1]),
25 |         (min_loc[0] + template_size[1], min_loc[1] + template_size[0]),
26 |         (255, 0, 0),
27 |         4)
28 |     return img, min_loc[0] + template_size[1] / 2, min_loc[1] +  template_size[0]
29 | 
30 | 
31 | def pull_screenshot():
32 |     filename = datetime.datetime.now().strftime("%H%M%S") + '.png'
33 |     os.system('mv autojump.png {}'.format(filename))
34 |     os.system('adb shell screencap -p /sdcard/autojump.png')
35 |     os.system('adb pull /sdcard/autojump.png ./autojump.png')
36 | 
37 | 
38 | def jump(distance):
39 |     press_time = distance * 1.35
40 |     press_time = int(press_time)
41 |     cmd = 'adb shell input swipe 320 410 320 410 ' + str(press_time)
42 |     print(cmd)
43 |     os.system(cmd)
44 | 
45 | 
46 | def update_data():
47 |     global src_x, src_y
48 | 
49 |     img = cv2.imread('./autojump.png')
50 |     img = cv2.resize(img, (0, 0), fx=scale, fy=scale)
51 |     img, src_x, src_y = search(img)
52 |     return img
53 | 
54 | 
55 | fig = plt.figure()
56 | pull_screenshot()
57 | img = update_data()
58 | im = plt.imshow(img, animated=True)
59 | 
60 | update = True
61 | 
62 | 
63 | def updatefig(*args):
64 |     global update
65 | 
66 |     if update:
67 |         time.sleep(1)
68 |         pull_screenshot()
69 |         im.set_array(update_data())
70 |         update = False
71 |     return im,
72 | 
73 | 
74 | def on_click(event):
75 |     global update    
76 |     global src_x, src_y
77 |     
78 |     dst_x, dst_y = event.xdata, event.ydata
79 | 
80 |     distance = (dst_x - src_x)**2 + (dst_y - src_y)**2 
81 |     distance = (distance ** 0.5) / scale
82 |     print('distance = ', distance)
83 |     jump(distance)
84 |     update = True
85 | 
86 | 
87 | fig.canvas.mpl_connect('button_press_event', on_click)
88 | ani = animation.FuncAnimation(fig, updatefig, interval=5, blit=True)
89 | plt.show()
90 | 


--------------------------------------------------------------------------------
/wechat_jump_auto.py:
--------------------------------------------------------------------------------
  1 | # -*- coding: utf-8 -*-
  2 | 
  3 | """
  4 | === 思路 ===
  5 | 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
  6 |     根据两个点的距离乘以一个时间系数获得长按的时间
  7 | 识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条
  8 |     直线,就从上往下一行一行遍历,比较颜色(颜色用了一个区间来比较)
  9 |     找到最下面的那一行的所有点,然后求个中点,求好之后再让 Y 轴坐标
 10 |     减小棋子底盘的一半高度从而得到中心点的坐标
 11 | 识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,
 12 |     由于圆形的块最顶上是一条线,方形的上面大概是一个点,所以就
 13 |     用类似识别棋子的做法多识别了几个点求中点,这时候得到了块中点的 X
 14 |     轴坐标,这时候假设现在棋子在当前块的中心,根据一个通过截图获取的
 15 |     固定的角度来推出中点的 Y 坐标
 16 | 最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
 17 | """
 18 | 
 19 | import math
 20 | import re
 21 | import random
 22 | import sys
 23 | import time
 24 | from PIL import Image
 25 | from six.moves import input
 26 | 
 27 | if sys.version_info.major != 3:
 28 |     print('请使用Python3')
 29 |     exit(1)
 30 | try:
 31 |     from common import debug, config, screenshot, UnicodeStreamFilter
 32 |     from common.auto_adb import auto_adb
 33 | except Exception as ex:
 34 |     print(ex)
 35 |     print('请将脚本放在项目根目录中运行')
 36 |     print('请检查项目根目录中的 common 文件夹是否存在')
 37 |     exit(1)
 38 | adb = auto_adb()
 39 | VERSION = "1.1.4"
 40 | 
 41 | # DEBUG 开关,需要调试的时候请改为 True,不需要调试的时候为 False
 42 | DEBUG_SWITCH = False
 43 | adb.test_device()
 44 | # Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需
 45 | # 设置,设置保存在 config 文件夹中
 46 | config = config.open_accordant_config()
 47 | under_game_score_y = config['under_game_score_y']
 48 | # 长按的时间系数,请自己根据实际情况调节
 49 | press_coefficient = config['press_coefficient']
 50 | # 二分之一的棋子底座高度,可能要调节
 51 | piece_base_height_1_2 = config['piece_base_height_1_2']
 52 | # 棋子的宽度,比截图中量到的稍微大一点比较安全,可能要调节
 53 | piece_body_width = config['piece_body_width']
 54 | # 图形中圆球的直径,可以利用系统自带画图工具,用直线测量像素,如果可以实现自动识别圆球直径,那么此处将可实现全自动。
 55 | head_diameter = config.get('head_diameter')
 56 | if head_diameter == None:
 57 |     density_str = adb.test_density()
 58 |     matches = re.search(r'\d+', density_str)
 59 |     density_val = int(matches.group(0))
 60 |     head_diameter = density_val / 8
 61 | 
 62 | 
 63 | def set_button_position(im):
 64 |     """
 65 |     将 swipe 设置为 `再来一局` 按钮的位置
 66 |     """
 67 |     global swipe_x1, swipe_y1, swipe_x2, swipe_y2
 68 |     w, h = im.size
 69 |     left = int(w / 2)
 70 |     top = int(1584 * (h / 1920.0))
 71 |     left = int(random.uniform(left - 200, left + 200))
 72 |     top = int(random.uniform(top - 200, top + 200))  # 随机防 ban
 73 |     after_top = int(random.uniform(top - 200, top + 200))
 74 |     after_left = int(random.uniform(left - 200, left + 200))
 75 |     swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, after_left, after_top
 76 | 
 77 | 
 78 | def jump(distance, delta_piece_y):
 79 |     """
 80 |     跳跃一定的距离
 81 |     """
 82 |     # 计算程序长度与截图测得的距离的比例
 83 |     scale = 0.945 * 2 / head_diameter
 84 |     actual_distance = distance * scale * (math.sqrt(6) / 2)
 85 |     press_time = (-945 + math.sqrt(945 ** 2 + 4 * 105 *
 86 |                                    36 * actual_distance)) / (2 * 105) * 1000
 87 |     press_time *= press_coefficient
 88 |     press_time = max(press_time, 200)  # 设置 200ms 是最小的按压时间
 89 |     press_time = int(press_time)
 90 | 
 91 |     cmd = 'shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(
 92 |         x1=swipe_x1,
 93 |         y1=swipe_y1,
 94 |         x2=swipe_x2,
 95 |         y2=swipe_y2,
 96 |         duration=press_time + delta_piece_y
 97 |     )
 98 |     print(cmd)
 99 |     adb.run(cmd)
100 |     return press_time
101 | 
102 | 
103 | def find_piece_and_board(im):
104 |     """
105 |     寻找关键坐标
106 |     """
107 |     w, h = im.size
108 |     points = []  # 所有满足色素的点集合
109 |     piece_y_max = 0
110 |     board_x = 0
111 |     board_y = 0
112 |     scan_x_border = int(w / 8)  # 扫描棋子时的左右边界
113 |     scan_start_y = 0  # 扫描的起始 y 坐标
114 |     im_pixel = im.load()
115 |     # 以 50px 步长,尝试探测 scan_start_y
116 |     for i in range(int(h / 3), int(h * 2 / 3), 50):
117 |         last_pixel = im_pixel[0, i]
118 |         for j in range(1, w):
119 |             pixel = im_pixel[j, i]
120 |             # 不是纯色的线,则记录 scan_start_y 的值,准备跳出循环
121 |             if pixel != last_pixel:
122 |                 scan_start_y = i - 50
123 |                 break
124 |         if scan_start_y:
125 |             break
126 |     print('start scan Y axis: {}'.format(scan_start_y))
127 | 
128 |     # 从 scan_start_y 开始往下扫描,棋子应位于屏幕上半部分,这里暂定不超过 2/3
129 |     for i in range(scan_start_y, int(h * 2 / 3)):
130 |         # 横坐标方面也减少了一部分扫描开销
131 |         for j in range(scan_x_border, w - scan_x_border):
132 |             pixel = im_pixel[j, i]
133 |             # 根据棋子的最低行的颜色判断,找最后一行那些点的平均值,这个颜
134 |             # 色这样应该 OK,暂时不提出来
135 |             if (50 < pixel[0] < 60) \
136 |                     and (53 < pixel[1] < 63) \
137 |                     and (95 < pixel[2] < 110):
138 |                 points.append((j, i))
139 |                 piece_y_max = max(i, piece_y_max)
140 | 
141 |     bottom_x = [x for x, y in points if y == piece_y_max]  # 所有最底层的点的横坐标
142 |     if not bottom_x:
143 |         return 0, 0, 0, 0, 0
144 | 
145 |     piece_x = int(sum(bottom_x) / len(bottom_x))  # 中间值
146 |     piece_y = piece_y_max - piece_base_height_1_2  # 上移棋子底盘高度的一半
147 | 
148 |     # 限制棋盘扫描的横坐标,避免音符 bug
149 |     if piece_x < w / 2:
150 |         board_x_start = piece_x
151 |         board_x_end = w
152 |     else:
153 |         board_x_start = 0
154 |         board_x_end = piece_x
155 | 
156 |     for i in range(int(h / 3), int(h * 2 / 3)):
157 |         last_pixel = im_pixel[0, i]
158 |         if board_x or board_y:
159 |             break
160 |         board_x_sum = 0
161 |         board_x_c = 0
162 | 
163 |         for j in range(int(board_x_start), int(board_x_end)):
164 |             pixel = im_pixel[j, i]
165 |             # 修掉脑袋比下一个小格子还高的情况的 bug
166 |             if abs(j - piece_x) < piece_body_width:
167 |                 continue
168 | 
169 |             # 检查Y轴下面5个像素, 和背景色相同, 那么是干扰
170 |             ver_pixel = im_pixel[j, i + 5]
171 |             if abs(pixel[0] - last_pixel[0]) \
172 |                     + abs(pixel[1] - last_pixel[1]) \
173 |                     + abs(pixel[2] - last_pixel[2]) > 10 \
174 |                     and abs(ver_pixel[0] - last_pixel[0]) \
175 |                     + abs(ver_pixel[1] - last_pixel[1]) \
176 |                     + abs(ver_pixel[2] - last_pixel[2]) > 10:
177 |                 board_x_sum += j
178 |                 board_x_c += 1
179 |         if board_x_sum:
180 |             board_x = board_x_sum / board_x_c
181 |     last_pixel = im_pixel[board_x, i]
182 | 
183 |     # 首先找到游戏的对称中心,由对称中心做辅助线与x=board_x直线的交点即为棋盘的中心位置
184 |     # 有了对称中心,可以知道棋子在棋盘上面的相对位置(偏高或偏低,偏高的话测量值比实际值大,
185 |     # 偏低相反。最后通过delta_piece_y来对跳跃时间进行微调
186 |     center_x = w / 2 + (24 / 1080) * w
187 |     center_y = h / 2 + (17 / 1920) * h
188 |     if piece_x > center_x:
189 |         board_y = round((25.5 / 43.5) * (board_x - center_x) + center_y)
190 |         delta_piece_y = piece_y - round((25.5 / 43.5) * (piece_x - center_x) + center_y)
191 |     else:
192 |         board_y = round(-(25.5 / 43.5) * (board_x - center_x) + center_y)
193 |         delta_piece_y = piece_y - round(-(25.5 / 43.5) * (piece_x - center_x) + center_y)
194 | 
195 |     if not all((board_x, board_y)):
196 |         return 0, 0, 0, 0, 0
197 |     return piece_x, piece_y, board_x, board_y, delta_piece_y
198 | 
199 | 
200 | def yes_or_no():
201 |     """
202 |     检查是否已经为启动程序做好了准备
203 |     """
204 |     while True:
205 |         yes_or_no = str(input('请确保手机打开了 ADB 并连接了电脑,'
206 |                               '然后打开跳一跳并【开始游戏】后再用本程序,确定开始?[y/n]:'))
207 |         if yes_or_no == 'y':
208 |             break
209 |         elif yes_or_no == 'n':
210 |             print('谢谢使用', end='')
211 |             exit(0)
212 |         else:
213 |             print('请重新输入')
214 | 
215 | 
216 | def main():
217 |     """
218 |     主函数
219 |     """
220 |     print('程序版本号:{}'.format(VERSION))
221 |     print('激活窗口并按 CONTROL + C 组合键退出')
222 |     debug.dump_device_info()
223 |     screenshot.check_screenshot()
224 | 
225 |     i, next_rest, next_rest_time = (0, random.randrange(3, 10),
226 |                                     random.randrange(5, 10))
227 |     while True:
228 |         im = screenshot.pull_screenshot()
229 |         # 获取棋子和 board 的位置
230 |         piece_x, piece_y, board_x, board_y, delta_piece_y = find_piece_and_board(im)
231 |         ts = int(time.time())
232 |         print(ts, piece_x, piece_y, board_x, board_y)
233 |         set_button_position(im)
234 |         jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2), delta_piece_y)
235 |         if DEBUG_SWITCH:
236 |             debug.save_debug_screenshot(ts, im, piece_x,
237 |                                         piece_y, board_x, board_y)
238 |             debug.backup_screenshot(ts)
239 |         im.close()
240 |         i += 1
241 |         if i == next_rest:
242 |             print('已经连续打了 {} 下,休息 {}秒'.format(i, next_rest_time))
243 |             for j in range(next_rest_time):
244 |                 sys.stdout.write('\r程序将在 {}秒 后继续'.format(next_rest_time - j))
245 |                 sys.stdout.flush()
246 |                 time.sleep(1)
247 |             print('\n继续')
248 |             i, next_rest, next_rest_time = (0, random.randrange(30, 100),
249 |                                             random.randrange(10, 60))
250 |         # 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
251 |         time.sleep(random.uniform(1.2, 1.4))
252 | 
253 | 
254 | if __name__ == '__main__':
255 |     try:
256 |         yes_or_no()
257 |         main()
258 |     except KeyboardInterrupt:
259 |         adb.run('kill-server')
260 |         print('\n谢谢使用', end='')
261 |         exit(0)
262 | 


--------------------------------------------------------------------------------
/wechat_jump_auto_ai.py:
--------------------------------------------------------------------------------
  1 | # coding: utf-8
  2 | '''
  3 | # === 思路 ===
  4 | # 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
  5 | #      根据两个点的距离乘以一个时间系数获得长按的时间
  6 | # 识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条直线,就从上往下一行一行遍历,
  7 | #      比较颜色(颜色用了一个区间来比较)找到最下面的那一行的所有点,然后求个中点,
  8 | #      求好之后再让 Y 轴坐标减小棋子底盘的一半高度从而得到中心点的坐标
  9 | # 识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,由于圆形的块最顶上是一条线,
 10 | #      方形的上面大概是一个点,所以就用类似识别棋子的做法多识别了几个点求中点,
 11 | #      这时候得到了块中点的 X 轴坐标,这时候假设现在棋子在当前块的中心,
 12 | #      根据一个通过截图获取的固定的角度来推出中点的 Y 坐标
 13 | # 最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
 14 | '''
 15 | import os
 16 | import sys
 17 | import subprocess
 18 | import time
 19 | import math
 20 | import pandas
 21 | from PIL import Image
 22 | import random
 23 | from six.moves import input
 24 | 
 25 | try:
 26 |     from common import ai, debug, config, UnicodeStreamFilter
 27 |     from common.auto_adb import auto_adb
 28 | except Exception as ex:
 29 |     print(ex)
 30 |     print('请将脚本放在项目根目录中运行')
 31 |     print('请检查项目根目录中的 common 文件夹是否存在')
 32 |     exit(1)
 33 | adb = auto_adb()
 34 | VERSION = "1.1.4"
 35 | 
 36 | debug_switch = True  # debug 开关,需要调试的时候请改为:True
 37 | config = config.open_accordant_config()
 38 | 
 39 | # Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需设置,设置保存在 config 文件夹中
 40 | under_game_score_y = config['under_game_score_y']
 41 | press_coefficient = config['press_coefficient']  # 长按的时间系数,请自己根据实际情况调节
 42 | piece_base_height_1_2 = config['piece_base_height_1_2']  # 二分之一的棋子底座高度,可能要调节
 43 | piece_body_width = config['piece_body_width']  # 棋子的宽度,比截图中量到的稍微大一点比较安全,可能要调节
 44 | 
 45 | screenshot_way = 2
 46 | 
 47 | 
 48 | def pull_screenshot():
 49 |     process = subprocess.Popen('adb shell screencap -p', shell=True, stdout=subprocess.PIPE)
 50 |     screenshot = process.stdout.read()
 51 |     if sys.platform == 'win32':
 52 |         screenshot = screenshot.replace(b'\r\n', b'\n')
 53 |     f = open('autojump.png', 'wb')
 54 |     f.write(screenshot)
 55 |     f.close()
 56 | 
 57 | 
 58 | def pull_screenshot_temp():
 59 |     process = subprocess.Popen('adb shell screencap -p', shell=True, stdout=subprocess.PIPE)
 60 |     screenshot = process.stdout.read()
 61 |     if sys.platform == 'win32':
 62 |         screenshot = screenshot.replace(b'\r\n', b'\n')
 63 |     f = open('autojump_temp.png', 'wb')
 64 |     f.write(screenshot)
 65 |     f.close()
 66 | 
 67 | 
 68 | def set_button_position(im):
 69 |     """
 70 |     将 swipe 设置为 `再来一局` 按钮的位置
 71 |     """
 72 |     global swipe_x1, swipe_y1, swipe_x2, swipe_y2
 73 |     w, h = im.size
 74 |     left = int(w / 2)
 75 |     top = int(1584 * (h / 1920.0))
 76 |     left = int(random.uniform(left - 100, left + 100))
 77 |     top = int(random.uniform(top - 100, top + 100))  # 随机防 ban
 78 |     after_top = int(random.uniform(top - 100, top + 100))
 79 |     after_left = int(random.uniform(left - 100, left + 100))
 80 |     swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, after_left, after_top
 81 | 
 82 | 
 83 | def jump(distance):
 84 |     '''
 85 |     跳跃一定的距离
 86 |     '''
 87 |     if ai.get_result_len() >= 10:  # 需采集10条样本以上
 88 |         k, b, v = ai.computing_k_b_v(distance)
 89 |         press_time = distance * k[0] + b
 90 |         print('Y = {k} * X + {b}'.format(k=k[0], b=b))
 91 | 
 92 |     else:
 93 |         press_time = distance * press_coefficient
 94 |         press_time = max(press_time, 200)  # 设置 200ms 是最小的按压时间
 95 | 
 96 |     press_time = int(press_time)
 97 |     cmd = 'shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(
 98 |         x1=swipe_x1,
 99 |         y1=swipe_y1,
100 |         x2=swipe_x2,
101 |         y2=swipe_y2,
102 |         duration=press_time
103 |     )
104 |     print('{}'.format(cmd))
105 |     adb.run(cmd)
106 |     return press_time
107 | 
108 | 
109 | # 转换色彩模式hsv2rgb
110 | def hsv2rgb(h, s, v):
111 |     h = float(h)
112 |     s = float(s)
113 |     v = float(v)
114 |     h60 = h / 60.0
115 |     h60f = math.floor(h60)
116 |     hi = int(h60f) % 6
117 |     f = h60 - h60f
118 |     p = v * (1 - s)
119 |     q = v * (1 - f * s)
120 |     t = v * (1 - (1 - f) * s)
121 |     r, g, b = 0, 0, 0
122 |     if hi == 0:
123 |         r, g, b = v, t, p
124 |     elif hi == 1:
125 |         r, g, b = q, v, p
126 |     elif hi == 2:
127 |         r, g, b = p, v, t
128 |     elif hi == 3:
129 |         r, g, b = p, q, v
130 |     elif hi == 4:
131 |         r, g, b = t, p, v
132 |     elif hi == 5:
133 |         r, g, b = v, p, q
134 |     r, g, b = int(r * 255), int(g * 255), int(b * 255)
135 |     return r, g, b
136 | 
137 | 
138 | # 转换色彩模式rgb2hsv
139 | def rgb2hsv(r, g, b):
140 |     r, g, b = r / 255.0, g / 255.0, b / 255.0
141 |     mx = max(r, g, b)
142 |     mn = min(r, g, b)
143 |     df = mx - mn
144 |     if mx == mn:
145 |         h = 0
146 |     elif mx == r:
147 |         h = (60 * ((g - b) / df) + 360) % 360
148 |     elif mx == g:
149 |         h = (60 * ((b - r) / df) + 120) % 360
150 |     elif mx == b:
151 |         h = (60 * ((r - g) / df) + 240) % 360
152 |     if mx == 0:
153 |         s = 0
154 |     else:
155 |         s = df / mx
156 |     v = mx
157 |     return h, s, v
158 | 
159 | 
160 | def find_piece(im):
161 |     '''
162 |     寻找关键坐标
163 |     '''
164 |     w, h = im.size
165 | 
166 |     piece_x_sum = 0
167 |     piece_x_c = 0
168 |     piece_y_max = 0
169 |     scan_x_border = int(w / 8)  # 扫描棋子时的左右边界
170 |     scan_start_y = 0  # 扫描的起始 y 坐标
171 |     im_pixel = im.load()
172 |     # 以 50px 步长,尝试探测 scan_start_y
173 |     for i in range(int(h / 3), int(h * 2 / 3), 50):
174 |         last_pixel = im_pixel[0, i]
175 |         for j in range(1, w):
176 |             pixel = im_pixel[j, i]
177 |             # 不是纯色的线,则记录 scan_start_y 的值,准备跳出循环
178 |             if pixel[0] != last_pixel[0] or pixel[1] != last_pixel[1] or pixel[2] != last_pixel[2]:
179 |                 scan_start_y = i - 50
180 |                 break
181 |         if scan_start_y:
182 |             break
183 |     # print('scan_start_y: {}'.format(scan_start_y))
184 | 
185 |     # 从 scan_start_y 开始往下扫描,棋子应位于屏幕上半部分,这里暂定不超过 2/3
186 |     for i in range(scan_start_y, int(h * 2 / 3)):
187 |         for j in range(scan_x_border, w - scan_x_border):  # 横坐标方面也减少了一部分扫描开销
188 |             pixel = im_pixel[j, i]
189 |             # 根据棋子的最低行的颜色判断,找最后一行那些点的平均值,这个颜色这样应该 OK,暂时不提出来
190 |             if (50 < pixel[0] < 60) and (53 < pixel[1] < 63) and (95 < pixel[2] < 110):
191 |                 piece_x_sum += j
192 |                 piece_x_c += 1
193 |                 piece_y_max = max(i, piece_y_max)
194 | 
195 |     if not all((piece_x_sum, piece_x_c)):
196 |         return 0, 0,
197 |     piece_x = int(piece_x_sum / piece_x_c)
198 |     piece_y = piece_y_max - piece_base_height_1_2  # 上移棋子底盘高度的一半
199 | 
200 |     return piece_x, piece_y
201 | 
202 | 
203 | def find_piece_and_board(im):
204 |     w, h = im.size
205 | 
206 |     piece_x_sum = 0
207 |     piece_x_c = 0
208 |     piece_y_max = 0
209 |     board_x = 0
210 |     board_y = 0
211 | 
212 |     left_value = 0
213 |     left_count = 0
214 |     right_value = 0
215 |     right_count = 0
216 |     from_left_find_board_y = 0
217 |     from_right_find_board_y = 0
218 | 
219 |     scan_x_border = int(w / 8)  # 扫描棋子时的左右边界
220 |     scan_start_y = 0  # 扫描的起始y坐标
221 |     im_pixel = im.load()
222 |     # 以50px步长,尝试探测scan_start_y
223 |     for i in range(int(h / 3), int(h * 2 / 3), 50):
224 |         last_pixel = im_pixel[0, i]
225 |         for j in range(1, w):
226 |             pixel = im_pixel[j, i]
227 |             # 不是纯色的线,则记录scan_start_y的值,准备跳出循环
228 |             if pixel[0] != last_pixel[0] or pixel[1] != last_pixel[1] or pixel[2] != last_pixel[2]:
229 |                 scan_start_y = i - 50
230 |                 break
231 |         if scan_start_y:
232 |             break
233 |     # print('scan_start_y: ', scan_start_y)
234 | 
235 |     # 从scan_start_y开始往下扫描,棋子应位于屏幕上半部分,这里暂定不超过2/3
236 |     for i in range(scan_start_y, int(h * 2 / 3)):
237 |         for j in range(scan_x_border, w - scan_x_border):  # 横坐标方面也减少了一部分扫描开销
238 |             pixel = im_pixel[j, i]
239 |             # 根据棋子的最低行的颜色判断,找最后一行那些点的平均值,这个颜色这样应该 OK,暂时不提出来
240 |             if (50 < pixel[0] < 60) and (53 < pixel[1] < 63) and (95 < pixel[2] < 110):
241 |                 piece_x_sum += j
242 |                 piece_x_c += 1
243 |                 piece_y_max = max(i, piece_y_max)
244 | 
245 |     if not all((piece_x_sum, piece_x_c)):
246 |         return 0, 0, 0, 0
247 |     piece_x = piece_x_sum / piece_x_c
248 |     piece_y = piece_y_max - piece_base_height_1_2  # 上移棋子底盘高度的一半
249 | 
250 |     for i in range(int(h / 3), int(h * 2 / 3)):
251 | 
252 |         last_pixel = im_pixel[0, i]
253 |         # 计算阴影的RGB值,通过photoshop观察,阴影部分其实就是背景色的明度V 乘以0.7的样子
254 |         h, s, v = rgb2hsv(last_pixel[0], last_pixel[1], last_pixel[2])
255 |         r, g, b = hsv2rgb(h, s, v * 0.7)
256 | 
257 |         if from_left_find_board_y and from_right_find_board_y:
258 |             break
259 | 
260 |         if not board_x:
261 |             board_x_sum = 0
262 |             board_x_c = 0
263 | 
264 |             for j in range(w):
265 |                 pixel = im_pixel[j, i]
266 |                 # 修掉脑袋比下一个小格子还高的情况的 bug
267 |                 if abs(j - piece_x) < piece_body_width:
268 |                     continue
269 | 
270 |                 # 修掉圆顶的时候一条线导致的小 bug,这个颜色判断应该 OK,暂时不提出来
271 |                 if abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) > 10:
272 |                     board_x_sum += j
273 |                     board_x_c += 1
274 |             if board_x_sum:
275 |                 board_x = board_x_sum / board_x_c
276 |         else:
277 |             # 继续往下查找,从左到右扫描,找到第一个与背景颜色不同的像素点,记录位置
278 |             # 当有连续3个相同的记录时,表示发现了一条直线
279 |             # 这条直线即为目标board的左边缘
280 |             # 然后当前的 y 值减 3 获得左边缘的第一个像素
281 |             # 就是顶部的左边顶点
282 |             for j in range(w):
283 |                 pixel = im_pixel[j, i]
284 |                 # 修掉脑袋比下一个小格子还高的情况的 bug
285 |                 if abs(j - piece_x) < piece_body_width:
286 |                     continue
287 |                 if (abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2])
288 |                     > 10) and (abs(pixel[0] - r) + abs(pixel[1] - g) + abs(pixel[2] - b) > 10):
289 |                     if left_value == j:
290 |                         left_count = left_count + 1
291 |                     else:
292 |                         left_value = j
293 |                         left_count = 1
294 | 
295 |                     if left_count > 3:
296 |                         from_left_find_board_y = i - 3
297 |                     break
298 |             # 逻辑跟上面类似,但是方向从右向左
299 |             # 当有遮挡时,只会有一边有遮挡
300 |             # 算出来两个必然有一个是对的
301 |             for j in range(w)[::-1]:
302 |                 pixel = im_pixel[j, i]
303 |                 # 修掉脑袋比下一个小格子还高的情况的 bug
304 |                 if abs(j - piece_x) < piece_body_width:
305 |                     continue
306 |                 if (abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2])
307 |                     > 10) and (abs(pixel[0] - r) + abs(pixel[1] - g) + abs(pixel[2] - b) > 10):
308 |                     if right_value == j:
309 |                         right_count = left_count + 1
310 |                     else:
311 |                         right_value = j
312 |                         right_count = 1
313 | 
314 |                     if right_count > 3:
315 |                         from_right_find_board_y = i - 3
316 |                     break
317 | 
318 |     # 如果顶部像素比较多,说明图案近圆形,相应的求出来的值需要增大,这里暂定增大顶部宽的三分之一
319 |     if board_x_c > 5:
320 |         from_left_find_board_y = from_left_find_board_y + board_x_c / 3
321 |         from_right_find_board_y = from_right_find_board_y + board_x_c / 3
322 | 
323 |     # 按实际的角度来算,找到接近下一个 board 中心的坐标 这里的角度应该是30°,值应该是tan 30°,math.sqrt(3) / 3
324 |     board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3
325 | 
326 |     # 从左从右取出两个数据进行对比,选出来更接近原来老算法的那个值
327 |     if abs(board_y - from_left_find_board_y) > abs(from_right_find_board_y):
328 |         new_board_y = from_right_find_board_y
329 |     else:
330 |         new_board_y = from_left_find_board_y
331 | 
332 |     if not all((board_x, board_y)):
333 |         return 0, 0, 0, 0
334 | 
335 |     return piece_x, piece_y, board_x, new_board_y
336 | 
337 | 
338 | def check_screenshot():
339 |     '''
340 |     检查获取截图的方式
341 |     '''
342 |     global screenshot_way
343 |     if os.path.isfile('autojump.png'):
344 |         os.remove('autojump.png')
345 |     if (screenshot_way < 0):
346 |         print('暂不支持当前设备')
347 |         sys.exit()
348 |     pull_screenshot()
349 |     try:
350 |         Image.open('./autojump.png').load()
351 |         print('采用方式 {} 获取截图'.format(screenshot_way))
352 |     except Exception:
353 |         screenshot_way -= 1
354 |         check_screenshot()
355 | 
356 | 
357 | def yes_or_no(prompt, true_value='y', false_value='n', default=True):
358 |     default_value = true_value if default else false_value
359 |     prompt = '%s %s/%s [%s]: ' % (prompt, true_value, false_value, default_value)
360 |     i = input(prompt)
361 |     if not i:
362 |         return default
363 |     while True:
364 |         if i == true_value:
365 |             return True
366 |         elif i == false_value:
367 |             return False
368 |         prompt = 'Please input %s or %s: ' % (true_value, false_value)
369 |         i = input(prompt)
370 | 
371 | 
372 | def main():
373 |     '''
374 |     主函数
375 |     '''
376 |     # op = yes_or_no('请确保手机打开了 ADB 并连接了电脑,然后打开跳一跳并【开始游戏】后再用本程序,确定开始?')
377 |     # if not op:
378 |     #    print('bye')
379 |     #    return
380 |     # 初始化AI
381 |     ai.init()
382 | 
383 |     print('程序版本号:{}'.format(VERSION))
384 |     debug.dump_device_info()
385 |     check_screenshot()
386 | 
387 |     i, next_rest, next_rest_time = 0, random.randrange(3, 10), random.randrange(5, 10)
388 |     while True:
389 |         pull_screenshot()
390 |         im = Image.open('./autojump.png')
391 |         # 获取棋子和 board 的位置
392 |         piece_x, piece_y, board_x, board_y = find_piece_and_board(im)
393 |         ts = int(time.time())
394 |         # print(ts, piece_x, piece_y, board_x, board_y)
395 |         set_button_position(im)
396 |         press_time = jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2))
397 | 
398 |         # 在跳跃落下的瞬间 摄像机移动前截图 这个参数要自己校调
399 |         time.sleep(0.2)
400 |         pull_screenshot_temp()
401 |         im_temp = Image.open('./autojump_temp.png')
402 |         temp_piece_x, temp_piece_y = find_piece(im_temp)
403 |         debug.computing_error(press_time, board_x, board_y, piece_x, piece_y, temp_piece_x, temp_piece_y)
404 | 
405 |         if debug_switch:
406 |             debug.save_debug_screenshot(ts, im, piece_x, piece_y, board_x, board_y)
407 |             debug.save_debug_screenshot(ts, im_temp, temp_piece_x, temp_piece_y, board_x, board_y)
408 |             # debug.backup_screenshot(ts)
409 |         i = 0
410 |         if i == next_rest:
411 |             print('已经连续打了 {} 下,休息 {}s'.format(i, next_rest_time))
412 |             for j in range(next_rest_time):
413 |                 sys.stdout.write('\r程序将在 {}s 后继续'.format(next_rest_time - j))
414 |                 sys.stdout.flush()
415 |                 time.sleep(1)
416 |             print('\n继续')
417 |             i, next_rest, next_rest_time = 0, random.randrange(30, 100), random.randrange(10, 60)
418 |         time.sleep(random.uniform(0.5, 0.6))  # 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
419 | 
420 | 
421 | if __name__ == '__main__':
422 |     try:
423 |         main()
424 |     except KeyboardInterrupt:
425 |         adb.run('kill-server')
426 |         print('bye')
427 |         exit(0)
428 | 


--------------------------------------------------------------------------------
/wechat_jump_auto_curves.py:
--------------------------------------------------------------------------------
  1 | # -*- coding: utf-8 -*-
  2 | 
  3 | """
  4 | ##基于python3.5(64位)
  5 | ###如果缺少scikit-image库,建议进下面网址下载whl直接安装
  6 | ##https://www.lfd.uci.edu/~gohlke/pythonlibs/#scikit-image
  7 | 
  8 | 
  9 | === 思路 ===
 10 | 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
 11 |     根据两个点的距离乘以一个时间系数获得长按的时间
 12 | 识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条
 13 |     直线,就从上往下一行一行遍历,比较颜色(颜色用了一个区间来比较)
 14 |     找到最下面的那一行的所有点,然后求个中点,求好之后再让 Y 轴坐标
 15 |     减小棋子底盘的一半高度从而得到中心点的坐标
 16 | 识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,
 17 |     由于圆形的块最顶上是一条线,方形的上面大概是一个点,所以就
 18 |     用类似识别棋子的做法多识别了几个点求中点,这时候得到了块中点的 X
 19 |     轴坐标,这时候假设现在棋子在当前块的中心,根据一个通过截图获取的
 20 |     固定的角度来推出中点的 Y 坐标
 21 | 最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
 22 | """
 23 | from __future__ import print_function, division
 24 | import sys
 25 | import time
 26 | import math
 27 | import random
 28 | from PIL import Image
 29 | from six.moves import input
 30 | from skimage import io,transform
 31 | import numpy as np
 32 | import tensorflow as tf
 33 | 
 34 | try:
 35 |     from common import debug, config, screenshot, UnicodeStreamFilter
 36 |     from common.auto_adb import auto_adb
 37 | except Exception as ex:
 38 |     print(ex)
 39 |     print('请将脚本放在项目根目录中运行')
 40 |     print('请检查项目根目录中的 common 文件夹是否存在')
 41 |     exit(1)
 42 | adb = auto_adb()
 43 | VERSION = "1.1.4"
 44 | 
 45 | # DEBUG 开关,需要调试的时候请改为 True,不需要调试的时候为 False
 46 | DEBUG_SWITCH = False
 47 | 
 48 | 
 49 | # Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需
 50 | # 设置,设置保存在 config 文件夹中
 51 | config = config.open_accordant_config()
 52 | under_game_score_y = config['under_game_score_y']
 53 | # 长按的时间系数,请自己根据实际情况调节
 54 | press_coefficient = config['press_coefficient']
 55 | # 二分之一的棋子底座高度,可能要调节
 56 | piece_base_height_1_2 = config['piece_base_height_1_2']
 57 | # 棋子的宽度,比截图中量到的稍微大一点比较安全,可能要调节
 58 | piece_body_width = config['piece_body_width']
 59 | 
 60 | target_score=1024         ##目标分数
 61 | total_step=30    ##达到目标次数所需游戏次数
 62 | start_score=100        ##设置第一次分数(目前分数)
 63 | 
 64 | 
 65 | 
 66 | def set_button_position(im):
 67 |     """
 68 |     将 swipe 设置为 `再来一局` 按钮的位置
 69 |     """
 70 |     global swipe_x1, swipe_y1, swipe_x2, swipe_y2
 71 |     w, h = im.size
 72 |     left = int(w / 2)
 73 |     top = int(1584 * (h / 1920.0))
 74 |     left = int(random.uniform(left - 100, left + 100))
 75 |     top = int(random.uniform(top - 100, top + 100))  # 随机防 ban
 76 |     after_top = int(random.uniform(top - 100, top + 100))
 77 |     after_left = int(random.uniform(left - 100, left + 100))
 78 |     swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, after_left, after_top
 79 | 
 80 | 
 81 | 
 82 | def jump(distance):
 83 |     """
 84 |     跳跃一定的距离
 85 |     """
 86 |     press_time = distance * press_coefficient
 87 |     press_time = max(press_time, 200)   # 设置 200ms 是最小的按压时间
 88 |     press_time = int(press_time)
 89 | 
 90 |     cmd = 'shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(
 91 |         x1=swipe_x1,
 92 |         y1=swipe_y1,
 93 |         x2=swipe_x2,
 94 |         y2=swipe_y2,
 95 |         duration=press_time
 96 |     )
 97 |     print('{} {}'.format(adb.adb_path, cmd))
 98 |     adb.run(cmd)
 99 |     return press_time
100 | 
101 | 
102 | def find_piece_and_board(im):
103 |     """
104 |     寻找关键坐标
105 |     """
106 |     w, h = im.size
107 | 
108 |     piece_x_sum = 0
109 |     piece_x_c = 0
110 |     piece_y_max = 0
111 |     board_x = 0
112 |     board_y = 0
113 |     scan_x_border = int(w / 8)  # 扫描棋子时的左右边界
114 |     scan_start_y = 0  # 扫描的起始 y 坐标
115 |     im_pixel = im.load()
116 |     # 以 50px 步长,尝试探测 scan_start_y
117 |     for i in range(int(h / 3), int(h*2 / 3), 50):
118 |         last_pixel = im_pixel[0, i]
119 |         for j in range(1, w):
120 |             pixel = im_pixel[j, i]
121 |             # 不是纯色的线,则记录 scan_start_y 的值,准备跳出循环
122 |             if pixel != last_pixel:
123 |                 scan_start_y = i - 50
124 |                 break
125 |         if scan_start_y:
126 |             break
127 |     print('scan_start_y: {}'.format(scan_start_y))
128 | 
129 |     # 从 scan_start_y 开始往下扫描,棋子应位于屏幕上半部分,这里暂定不超过 2/3
130 |     for i in range(scan_start_y, int(h * 2 / 3)):
131 |         # 横坐标方面也减少了一部分扫描开销
132 |         for j in range(scan_x_border, w - scan_x_border):
133 |             pixel = im_pixel[j, i]
134 |             # 根据棋子的最低行的颜色判断,找最后一行那些点的平均值,这个颜
135 |             # 色这样应该 OK,暂时不提出来
136 |             if (50 < pixel[0] < 60) \
137 |                     and (53 < pixel[1] < 63) \
138 |                     and (95 < pixel[2] < 110):
139 |                 piece_x_sum += j
140 |                 piece_x_c += 1
141 |                 piece_y_max = max(i, piece_y_max)
142 | 
143 |     if not all((piece_x_sum, piece_x_c)):
144 |         return 0, 0, 0, 0
145 |     piece_x = int(piece_x_sum / piece_x_c)
146 |     piece_y = piece_y_max - piece_base_height_1_2  # 上移棋子底盘高度的一半
147 | 
148 |     # 限制棋盘扫描的横坐标,避免音符 bug
149 |     if piece_x < w/2:
150 |         board_x_start = piece_x
151 |         board_x_end = w
152 |     else:
153 |         board_x_start = 0
154 |         board_x_end = piece_x
155 | 
156 |     for i in range(int(h / 3), int(h * 2 / 3)):
157 |         last_pixel = im_pixel[0, i]
158 |         if board_x or board_y:
159 |             break
160 |         board_x_sum = 0
161 |         board_x_c = 0
162 | 
163 |         for j in range(int(board_x_start), int(board_x_end)):
164 |             pixel = im_pixel[j, i]
165 |             # 修掉脑袋比下一个小格子还高的情况的 bug
166 |             if abs(j - piece_x) < piece_body_width:
167 |                 continue
168 | 
169 |             # 修掉圆顶的时候一条线导致的小 bug,这个颜色判断应该 OK,暂时不提出来
170 |             if abs(pixel[0] - last_pixel[0]) \
171 |                     + abs(pixel[1] - last_pixel[1]) \
172 |                     + abs(pixel[2] - last_pixel[2]) > 10:
173 |                 board_x_sum += j
174 |                 board_x_c += 1
175 |         if board_x_sum:
176 |             board_x = board_x_sum / board_x_c
177 |     last_pixel = im_pixel[board_x, i]
178 | 
179 |     # 从上顶点往下 +274 的位置开始向上找颜色与上顶点一样的点,为下顶点
180 |     # 该方法对所有纯色平面和部分非纯色平面有效,对高尔夫草坪面、木纹桌面、
181 |     # 药瓶和非菱形的碟机(好像是)会判断错误
182 |     for k in range(i+274, i, -1):  # 274 取开局时最大的方块的上下顶点距离
183 |         pixel = im_pixel[board_x, k]
184 |         if abs(pixel[0] - last_pixel[0]) \
185 |                 + abs(pixel[1] - last_pixel[1]) \
186 |                 + abs(pixel[2] - last_pixel[2]) < 10:
187 |             break
188 |     board_y = int((i+k) / 2)
189 | 
190 |     # 如果上一跳命中中间,则下个目标中心会出现 r245 g245 b245 的点,利用这个
191 |     # 属性弥补上一段代码可能存在的判断错误
192 |     # 若上一跳由于某种原因没有跳到正中间,而下一跳恰好有无法正确识别花纹,则有
193 |     # 可能游戏失败,由于花纹面积通常比较大,失败概率较低
194 |     for j in range(i, i+200):
195 |         pixel = im_pixel[board_x, j]
196 |         if abs(pixel[0] - 245) + abs(pixel[1] - 245) + abs(pixel[2] - 245) == 0:
197 |             board_y = j + 10
198 |             break
199 | 
200 |     if not all((board_x, board_y)):
201 |         return 0, 0, 0, 0
202 |     return piece_x, piece_y, board_x, board_y
203 | 
204 | 
205 | def yes_or_no(prompt, true_value='y', false_value='n', default=True):
206 |     """
207 |     检查是否已经为启动程序做好了准备
208 |     """
209 |     default_value = true_value if default else false_value
210 |     prompt = '{} {}/{} [{}]: '.format(prompt, true_value,
211 |         false_value, default_value)
212 |     i = input(prompt)
213 |     if not i:
214 |         return default
215 |     while True:
216 |         if i == true_value:
217 |             return True
218 |         elif i == false_value:
219 |             return False
220 |         prompt = 'Please input {} or {}: '.format(true_value, false_value)
221 |         i = input(prompt)
222 | 
223 | def pross_data(image):
224 |     pixels = list(image.getdata())  # 得到像素数据 灰度0-255
225 |     #print(len(pixels))
226 |     for i in range(len(pixels)):
227 |         if pixels[i]<100:
228 |             pixels[i]=0
229 |         else:
230 |             pixels[i]=255
231 |     return pixels
232 | 
233 | def pixel_division(img,w,h):
234 |     pixels = list(img.getdata())
235 |     row_pix=np.zeros([1,h])
236 |     col_pix=np.zeros([1,w])
237 |     for i in range(w):
238 |         for j in range(h):
239 |             if pixels[j*w+i]<100:
240 |                 row_pix[0,j]+=1
241 |                 col_pix[0,i]+=1
242 |     start_h=0
243 |     end_h=0
244 |     flag=0
245 |     for j in range(h):
246 |         if row_pix[0,j]>=1 and flag==0:
247 |             start_h=j
248 |             flag=1
249 |         if row_pix[0,j]>=1:
250 |             end_h=j
251 | 
252 |     pixels_Widh=[]
253 |     end_w=0
254 |     for i in range(1,w):
255 |         if col_pix[0,i-1]<=0 and col_pix[0,i]>=1:
256 |             pixels_Widh.append(i-1)
257 |         if col_pix[0,i]>=1:
258 |             end_w=i
259 |     pixels_Widh.append(end_w+1)
260 |     return start_h,end_h,pixels_Widh
261 | 
262 | def strint(score0):
263 |     if(score0<10):
264 |         return str(score0)
265 |     else:
266 |         return ""
267 | 
268 | def read_one_image(path):
269 |     img = io.imread(path)
270 |     w=81
271 |     h=81
272 |     c=1
273 |     img = transform.resize(img,(w,h,c))
274 |     return np.asarray(img)
275 | 
276 | def main():
277 |     """
278 |     主函数
279 |     """
280 |     op = yes_or_no('请确保手机打开了 ADB 并连接了电脑,'
281 |                    '然后打开跳一跳并【开始游戏】后再用本程序,确定开始?')
282 |     if not op:
283 |         print('bye')
284 |         return
285 |     print('程序版本号:{}'.format(VERSION))
286 |     debug.dump_device_info()
287 |     screenshot.check_screenshot()
288 | 
289 |     i, next_rest, next_rest_time = (0, random.randrange(3, 10),
290 |                                     random.randrange(5, 10))
291 |     j= 0
292 |     ################ 分数曲线公式
293 | 
294 |     y_score=[]
295 |     next_start=0
296 |     global start_score
297 |     for i in range(total_step):
298 |         each_score=target_score*(1-np.exp(-0.15*(1024.0/target_score)*i))
299 |         y_score.append(each_score)
300 |         if start_score>each_score:
301 |             next_start=i
302 |     next_start+=1
303 |     #print(y_score)
304 |     if start_score<y_score[0]:
305 |         next_start=0
306 | 
307 |     ###################
308 |     with tf.Session() as sess:
309 |         saver = tf.train.import_meta_graph('./resource/model/model.ckpt.meta')
310 |         saver.restore(sess,tf.train.latest_checkpoint('./resource/model/'))
311 | 
312 |         graph = tf.get_default_graph()
313 |         x = graph.get_tensor_by_name("x:0")
314 |         logits = graph.get_tensor_by_name("logits_eval:0")
315 |     #####################识别分数
316 |         while True:
317 |             screenshot.pull_screenshot()
318 |             im = Image.open('./autojump.png')
319 |             ##比例系数
320 |             pix_w=im.size[0]*1.0/1080
321 |             pix_h=im.size[1]
322 |             region=im.crop((0,pix_h*0.1,460*pix_w,pix_h*0.2))
323 |             region=region.convert('L')
324 |             start_h,end_h,pixels_Widh=pixel_division(region,int(460*pix_w),int(pix_h*0.1))
325 |             if start_h==end_h:
326 |                 continue
327 |             data = []
328 |             for i in range(len(pixels_Widh)-1):
329 |                 region1=region.crop((pixels_Widh[i],start_h,pixels_Widh[i+1],end_h))
330 |                 region1.putdata(pross_data(region1))
331 |                 str1="./region"+str(i)+".png"
332 |                 region1.save(str1)
333 |                 data1 = read_one_image(str1)
334 |                 data.append(data1)
335 |             feed_dict = {x:data}
336 |             classification_result = sess.run(logits,feed_dict)
337 |             output = []
338 |             output = tf.argmax(classification_result,1).eval()
339 |             m_score=""
340 |             for i in range(len(output)):
341 |                 m_score+=strint(output[i])
342 |             if m_score=="":
343 |                 continue
344 |             m_score=int(m_score)
345 |             print('score:{}'.format(m_score))
346 |             ####################################
347 |             # 获取棋子和 board 的位置
348 |             print(j)
349 |             piece_x, piece_y, board_x, board_y = find_piece_and_board(im)
350 |             ts = int(time.time())
351 |             print(ts, piece_x, piece_y, board_x, board_y)
352 |             set_button_position(im)
353 | 
354 |             if m_score > y_score[next_start]: ##自动结束这一次
355 |                 print("----------------")
356 |                 jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2)*5)
357 |                 next_start+=1
358 |                 time.sleep(5*random.random())
359 |             if next_start >len(y_score):
360 |                 break
361 |             jump(math.sqrt((board_x - piece_x) ** 2 + (board_y - piece_y) ** 2))
362 |             if DEBUG_SWITCH:
363 |                 debug.save_debug_screenshot(ts, im, piece_x,
364 |                                             piece_y, board_x, board_y)
365 |                 debug.backup_screenshot(ts)
366 |             im.close()
367 |             i += 1
368 |             j += 1
369 |             if i == next_rest:
370 |                 print('已经连续打了 {} 下,休息 {}s'.format(i, next_rest_time))
371 |                 for j in range(next_rest_time):
372 |                     sys.stdout.write('\r程序将在 {}s 后继续'.format(next_rest_time - j))
373 |                     sys.stdout.flush()
374 |                     time.sleep(1)
375 |                 print('\n继续')
376 |                 i, next_rest, next_rest_time = (0, random.randrange(30, 100),
377 |                                                 random.randrange(10, 60))
378 |             # 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
379 |             time.sleep(random.uniform(0.9, 1.2))
380 | 
381 | 
382 | if __name__ == '__main__':
383 |     try:
384 |         main()
385 |     except KeyboardInterrupt:
386 |         adb.run('kill-server')
387 |         print('bye')
388 |         exit(0)
389 | 


--------------------------------------------------------------------------------
/wechat_jump_auto_iOS.py:
--------------------------------------------------------------------------------
  1 | # -*- coding: utf-8 -*-
  2 | 
  3 | """
  4 | # === 思路 ===
  5 | # 核心:每次落稳之后截图,根据截图算出棋子的坐标和下一个块顶面的中点坐标,
  6 | #      根据两个点的距离乘以一个时间系数获得长按的时间
  7 | # 识别棋子:靠棋子的颜色来识别位置,通过截图发现最下面一行大概是一条
  8 |            直线,就从上往下一行一行遍历,比较颜色(颜色用了一个区间来比较)
  9 |            找到最下面的那一行的所有点,然后求个中点,求好之后再让 Y 轴坐标
 10 |            减小棋子底盘的一半高度从而得到中心点的坐标
 11 | # 识别棋盘:靠底色和方块的色差来做,从分数之下的位置开始,一行一行扫描,
 12 |            由于圆形的块最顶上是一条线,方形的上面大概是一个点,所以就
 13 |            用类似识别棋子的做法多识别了几个点求中点,这时候得到了块中点的 X
 14 |            轴坐标,这时候假设现在棋子在当前块的中心,根据一个通过截图获取的
 15 |            固定的角度来推出中点的 Y 坐标
 16 | # 最后:根据两点的坐标算距离乘以系数来获取长按时间(似乎可以直接用 X 轴距离)
 17 | """
 18 | import os
 19 | import shutil
 20 | import time
 21 | import math
 22 | import random
 23 | import json
 24 | from PIL import Image, ImageDraw
 25 | import wda
 26 | 
 27 | 
 28 | with open('config.json', 'r') as f:
 29 |     config = json.load(f)
 30 | 
 31 | 
 32 | # Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需设置
 33 | under_game_score_y = config['under_game_score_y']
 34 | # 长按的时间系数,请自己根据实际情况调节
 35 | press_coefficient = config['press_coefficient']
 36 | # 二分之一的棋子底座高度,可能要调节
 37 | piece_base_height_1_2 = config['piece_base_height_1_2']
 38 | # 棋子的宽度,比截图中量到的稍微大一点比较安全,可能要调节
 39 | piece_body_width = config['piece_body_width']
 40 | time_coefficient = config['press_coefficient']
 41 | 
 42 | # 模拟按压的起始点坐标,需要自动重复游戏请设置成“再来一局”的坐标
 43 | swipe = config.get('swipe', {
 44 |     "x1": 320,
 45 |     "y1": 410,
 46 |     "x2": 320,
 47 |     "y2": 410
 48 |     })
 49 | VERSION = "1.1.4"
 50 | c = wda.Client()
 51 | s = c.session()
 52 | 
 53 | screenshot_backup_dir = 'screenshot_backups/'
 54 | if not os.path.isdir(screenshot_backup_dir):
 55 |     os.mkdir(screenshot_backup_dir)
 56 | 
 57 | 
 58 | def pull_screenshot():
 59 |     c.screenshot('1.png')
 60 | 
 61 | 
 62 | def jump(distance):
 63 |     press_time = distance * time_coefficient / 1000
 64 |     print('press time: {}'.format(press_time))
 65 |     s.tap_hold(random.uniform(0, 320), random.uniform(64, 320), press_time)
 66 | 
 67 | 
 68 | def backup_screenshot(ts):
 69 |     """
 70 |     为了方便失败的时候 debug
 71 |     """
 72 |     if not os.path.isdir(screenshot_backup_dir):
 73 |         os.mkdir(screenshot_backup_dir)
 74 |     shutil.copy('1.png', '{}{}.png'.format(screenshot_backup_dir, ts))
 75 | 
 76 | 
 77 | def save_debug_creenshot(ts, im, piece_x, piece_y, board_x, board_y):
 78 |     draw = ImageDraw.Draw(im)
 79 |     # 对debug图片加上详细的注释
 80 |     draw.line((piece_x, piece_y) + (board_x, board_y), fill=2, width=3)
 81 |     draw.line((piece_x, 0, piece_x, im.size[1]), fill=(255, 0, 0))
 82 |     draw.line((0, piece_y, im.size[0], piece_y), fill=(255, 0, 0))
 83 |     draw.line((board_x, 0, board_x, im.size[1]), fill=(0, 0, 255))
 84 |     draw.line((0, board_y, im.size[0], board_y), fill=(0, 0, 255))
 85 |     draw.ellipse(
 86 |         (piece_x - 10, piece_y - 10, piece_x + 10, piece_y + 10),
 87 |         fill=(255, 0, 0))
 88 |     draw.ellipse(
 89 |         (board_x - 10, board_y - 10, board_x + 10, board_y + 10),
 90 |         fill=(0, 0, 255))
 91 |     del draw
 92 |     im.save('{}{}_d.png'.format(screenshot_backup_dir, ts))
 93 | 
 94 | 
 95 | def set_button_position(im):
 96 |     """
 97 |     将swipe设置为 `再来一局` 按钮的位置
 98 |     """
 99 |     global swipe_x1, swipe_y1, swipe_x2, swipe_y2
100 |     w, h = im.size
101 |     left = w / 2
102 |     top = 1003 * (h / 1280.0) + 10
103 |     swipe_x1, swipe_y1, swipe_x2, swipe_y2 = left, top, left, top
104 | 
105 | 
106 | def find_piece_and_board(im):
107 |     w, h = im.size
108 | 
109 |     print("size: {}, {}".format(w, h))
110 | 
111 |     piece_x_sum = piece_x_c = piece_y_max = 0
112 |     board_x = board_y = 0
113 |     scan_x_border = int(w / 8)  # 扫描棋子时的左右边界
114 |     scan_start_y = 0  # 扫描的起始 y 坐标
115 |     im_pixel = im.load()
116 | 
117 |     # 以 50px 步长,尝试探测 scan_start_y
118 |     for i in range(under_game_score_y, h, 50):
119 |         last_pixel = im_pixel[0, i]
120 |         for j in range(1, w):
121 |             pixel = im_pixel[j, i]
122 | 
123 |             # 不是纯色的线,则记录scan_start_y的值,准备跳出循环
124 |             if pixel != last_pixel:
125 |                 scan_start_y = i - 50
126 |                 break
127 | 
128 |         if scan_start_y:
129 |             break
130 | 
131 |     print("scan_start_y: ", scan_start_y)
132 | 
133 |     # 从 scan_start_y 开始往下扫描,棋子应位于屏幕上半部分,这里暂定不超过 2/3
134 |     for i in range(scan_start_y, int(h * 2 / 3)):
135 |         # 横坐标方面也减少了一部分扫描开销
136 |         for j in range(scan_x_border, w - scan_x_border):
137 |             pixel = im_pixel[j, i]
138 |             # 根据棋子的最低行的颜色判断,找最后一行那些点的平均值,这个颜
139 |             # 色这样应该 OK,暂时不提出来
140 |             if (50 < pixel[0] < 60) \
141 |                     and (53 < pixel[1] < 63) \
142 |                     and (95 < pixel[2] < 110):
143 |                 piece_x_sum += j
144 |                 piece_x_c += 1
145 |                 piece_y_max = max(i, piece_y_max)
146 | 
147 |     if not all((piece_x_sum, piece_x_c)):
148 |         return 0, 0, 0, 0
149 |     piece_x = piece_x_sum / piece_x_c
150 |     piece_y = piece_y_max - piece_base_height_1_2  # 上移棋子底盘高度的一半
151 | 
152 |     for i in range(int(h / 3), int(h * 2 / 3)):
153 |         last_pixel = im_pixel[0, i]
154 |         if board_x or board_y:
155 |             break
156 |         board_x_sum = 0
157 |         board_x_c = 0
158 | 
159 |         for j in range(w):
160 |             pixel = im_pixel[j, i]
161 |             # 修掉脑袋比下一个小格子还高的情况的 bug
162 |             if abs(j - piece_x) < piece_body_width:
163 |                 continue
164 | 
165 |             # 修掉圆顶的时候一条线导致的小 bug,这个颜色判断应该 OK,暂时不提出来
166 |             if abs(pixel[0] - last_pixel[0]) \
167 |                     + abs(pixel[1] - last_pixel[1]) \
168 |                     + abs(pixel[2] - last_pixel[2]) > 10:
169 |                 board_x_sum += j
170 |                 board_x_c += 1
171 | 
172 |         if board_x_sum:
173 |             board_x = board_x_sum / board_x_c
174 | 
175 |     # 按实际的角度来算,找到接近下一个 board 中心的坐标 这里的角度应该
176 |     # 是 30°,值应该是 tan 30°, math.sqrt(3) / 3
177 |     board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3
178 | 
179 |     if not all((board_x, board_y)):
180 |         return 0, 0, 0, 0
181 | 
182 |     return piece_x, piece_y, board_x, board_y
183 | 
184 | 
185 | def main():
186 |     while True:
187 |         pull_screenshot()
188 |         im = Image.open("./1.png")
189 | 
190 |         # 获取棋子和 board 的位置
191 |         piece_x, piece_y, board_x, board_y = find_piece_and_board(im)
192 |         ts = int(time.time())
193 |         print(ts, piece_x, piece_y, board_x, board_y)
194 |         if piece_x == 0:
195 |             return
196 | 
197 |         set_button_position(im)
198 |         distance = math.sqrt(
199 |             (board_x - piece_x) ** 2 + (board_y - piece_y) ** 2)
200 |         jump(distance)
201 | 
202 |         save_debug_creenshot(ts, im, piece_x, piece_y, board_x, board_y)
203 |         backup_screenshot(ts)
204 |         # 为了保证截图的时候应落稳了,多延迟一会儿,随机值防 ban
205 |         time.sleep(random.uniform(1, 1.1))
206 | 
207 | 
208 | if __name__ == '__main__':
209 |     main()
210 | 


--------------------------------------------------------------------------------
/wechat_jump_auto_slim.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env python3
  2 | # -*- coding: utf-8 -*-
  3 | __author__ = 'Erimus'
  4 | '''
  5 | 这个是精简版本,只取x轴距离。
  6 | 可以适配任意屏幕。
  7 | 把磁盘读写截图改为内存读写。
  8 | 可以防止被ban(从抓包数据看没有返回Error)。
  9 | '''
 10 | 
 11 | import os
 12 | import sys
 13 | import subprocess
 14 | import time
 15 | import random
 16 | from PIL import Image, ImageDraw
 17 | from io import BytesIO
 18 | 
 19 | # ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
 20 | VERSION = "1.1.4"
 21 | screenshot_way = 2
 22 | 
 23 | 
 24 | def check_screenshot():  # 检查获取截图的方式
 25 |     global screenshot_way
 26 |     if (screenshot_way < 0):
 27 |         print('暂不支持当前设备')
 28 |         sys.exit()
 29 |     binary_screenshot = pull_screenshot()
 30 |     try:
 31 |         Image.open(BytesIO(binary_screenshot)).load()  # 直接使用内存IO
 32 |         print('Capture Method: {}'.format(screenshot_way))
 33 |     except Exception:
 34 |         screenshot_way -= 1
 35 |         check_screenshot()
 36 | 
 37 | 
 38 | def pull_screenshot():  # 获取截图
 39 |     global screenshot_way
 40 |     if screenshot_way in [1, 2]:
 41 |         process = subprocess.Popen(
 42 |             'adb shell screencap -p', shell=True, stdout=subprocess.PIPE)
 43 |         screenshot = process.stdout.read()
 44 |         if screenshot_way == 2:
 45 |             binary_screenshot = screenshot.replace(b'\r\n', b'\n')
 46 |         else:
 47 |             binary_screenshot = screenshot.replace(b'\r\r\n', b'\n')
 48 |         return binary_screenshot
 49 |     elif screenshot_way == 0:
 50 |         os.system('adb shell screencap -p /sdcard/autojump.png')
 51 |         os.system('adb pull /sdcard/autojump.png .')
 52 | 
 53 | # ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
 54 | 
 55 | 
 56 | def find_piece_and_board(im):  # 寻找起点和终点坐标
 57 |     w, h = im.size  # 图片宽高
 58 |     im_pixel = im.load()
 59 | 
 60 |     def find_piece(pixel):  # 棋子取色精确范围
 61 |         return ((40 < pixel[0] < 65) and
 62 |                 (40 < pixel[1] < 65) and
 63 |                 (80 < pixel[2] < 105))
 64 | 
 65 |     # 寻找棋子 ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
 66 | 
 67 |     # 粗查棋子位置
 68 |     piece_found, piece_fx, piece_fy = 0, 0, 0
 69 |     scan_piece_unit = w // 40  # 间隔单位
 70 |     ny = (h + w) // 2  # 寻找下限 从画面中央的正方形的下缘开始
 71 |     while ny > (h - w) // 2 and not piece_found:
 72 |         ny -= scan_piece_unit
 73 |         for nx in range(0, w, scan_piece_unit):
 74 |             pixel = im_pixel[nx, ny]
 75 |             if find_piece(pixel):
 76 |                 piece_fx, piece_fy = nx, ny
 77 |                 piece_found = True
 78 |                 break
 79 |     print('%-12s %s,%s' % ('piece_fuzzy:', piece_fx, piece_fy))
 80 |     if not piece_fx:
 81 |         return 0, 0  # 没找到棋子
 82 | 
 83 |     # 精查棋子位置
 84 |     piece_x, piece_x_set = 0, []  # 棋子x/棋子坐标集合
 85 |     piece_width = w // 14  # 估算棋子宽度
 86 |     piece_height = w // 5  # 估算棋子高度
 87 |     for ny in range(piece_fy + scan_piece_unit, piece_fy - piece_height, -4):
 88 |         for nx in range(max(piece_fx - piece_width, 0),
 89 |                         min(piece_fx + piece_width, w)):
 90 |             pixel = im_pixel[nx, ny]
 91 |             # print(nx,ny,pixel)
 92 |             if find_piece(pixel):
 93 |                 piece_x_set.append(nx)
 94 |         if len(piece_x_set) > 10:
 95 |             piece_x = sum(piece_x_set) / len(piece_x_set)
 96 |             break
 97 |     print('%-12s %s' % ('p_exact_x:', piece_x))
 98 | 
 99 |     # 寻找落点 ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
100 |     board_x = 0
101 |     # 限制棋盘扫描的横坐标 避免音符bug
102 |     if piece_x < w / 2:
103 |         board_x_start, board_x_end = w // 2, w  # 起点和终点的中点是画面中心
104 |     else:
105 |         board_x_start, board_x_end = 0, w // 2
106 | 
107 |     # 寻找落点顶点
108 |     board_x_set = []  # 目标坐标集合/改为list避免去重
109 |     for by in range((h - w) // 2, (h + w) // 2, 4):
110 |         bg_pixel = im_pixel[0, by]
111 |         for bx in range(board_x_start, board_x_end):
112 |             pixel = im_pixel[bx, by]
113 |             # 修掉脑袋比下一个小格子还高的情况 屏蔽小人左右的范围
114 |             if abs(bx - piece_x) < piece_width:
115 |                 continue
116 | 
117 |             # 修掉圆顶的时候一条线导致的小bug 这个颜色判断应该OK
118 |             if (abs(pixel[0] - bg_pixel[0]) +
119 |                     abs(pixel[1] - bg_pixel[1]) +
120 |                     abs(pixel[2] - bg_pixel[2]) > 10):
121 |                 board_x_set.append(bx)
122 | 
123 |         if len(board_x_set) > 10:
124 |             board_x = sum(board_x_set) / len(board_x_set)
125 |             print('%-12s %s' % ('target_x:', board_x))
126 |             break  # 找到了退出
127 | 
128 |     return piece_x, board_x
129 | 
130 | # ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
131 | 
132 | 
133 | def set_button_position(im, gameover=0):  # 重设点击位置 再来一局位置
134 |     w, h = im.size
135 |     if h // 16 > w // 9 + 2:  # 长窄屏 2px容差 获取ui描绘的高度
136 |         uih = int(w / 9 * 16)
137 |     else:
138 |         uih = h
139 |     # uiw = int(uih / 16 * 9)
140 | 
141 |     # 如果游戏结束 点击再来一局
142 |     left = int(w / 2)  # 按钮半宽约uiw//5
143 |     # 根据9:16实测按钮高度中心0.825 按钮半高约uiw//28
144 |     top = int((h - uih) / 2 + uih * 0.825)
145 |     if gameover:
146 |         return left, top
147 | 
148 |     # 游戏中点击 随机位置防 ban
149 |     left = random.randint(w // 4, w - 20)  # 避开左下角按钮
150 |     top = random.randint(h * 3 // 4, h - 20)
151 |     return left, top
152 | 
153 | # ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
154 | 
155 | 
156 | def jump(piece_x, board_x, im, swipe_x1, swipe_y1):
157 |     distanceX = abs(board_x - piece_x)  # 起点到目标的水平距离
158 |     shortEdge = min(im.size)  # 屏幕宽度
159 |     jumpPercent = distanceX / shortEdge  # 跳跃百分比
160 |     jumpFullWidth = 1700  # 跳过整个宽度 需要按压的毫秒数
161 |     press_time = round(jumpFullWidth * jumpPercent)  # 按压时长
162 |     press_time = 0 if not press_time else max(
163 |         press_time, 200)  # press_time大于0时限定最小值
164 |     print('%-12s %.2f%% (%s/%s) | Press: %sms' %
165 |           ('Distance:', jumpPercent * 100, distanceX, shortEdge, press_time))
166 | 
167 |     cmd = 'adb shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(
168 |         x1=swipe_x1,
169 |         y1=swipe_y1,
170 |         x2=swipe_x1 + random.randint(-10, 10),  # 模拟位移
171 |         y2=swipe_y1 + random.randint(-10, 10),
172 |         duration=press_time
173 |     )
174 |     os.system(cmd)
175 | 
176 | # ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
177 | 
178 | 
179 | def main():
180 |     check_screenshot()  # 检查截图
181 | 
182 |     count = 0
183 |     while True:
184 |         count += 1
185 |         print('---\n%-12s %s (%s)' % ('Times:', count, int(time.time())))
186 | 
187 |         # 获取截图
188 |         binary_screenshot = pull_screenshot()
189 |         im = Image.open(BytesIO(binary_screenshot))
190 |         w, h = im.size
191 |         if w > h:
192 |             im = im.rotate(-90, expand=True)  # 添加图片方向判断
193 |         # print('image | w:%s | h:%s'%(w,h))
194 | 
195 |         # 获取棋子和 board 的位置
196 |         piece_x, board_x = find_piece_and_board(im)
197 |         gameover = 0 if all((piece_x, board_x)) else 1
198 |         swipe_x1, swipe_y1 = set_button_position(
199 |             im, gameover=gameover)  # 随机点击位置
200 | 
201 |         # 标注截图并显示
202 |         # draw = ImageDraw.Draw(im)
203 |         # draw.line([piece_x, 0, piece_x, h], fill='blue', width=1)  # start
204 |         # draw.line([board_x, 0, board_x, h], fill='red', width=1)  # end
205 |         # draw.ellipse([swipe_x1 - 16, swipe_y1 - 16,
206 |         #               swipe_x1 + 16, swipe_y1 + 16], fill='red')  # click
207 |         # im.show()
208 | 
209 |         jump(piece_x, board_x, im, swipe_x1, swipe_y1)
210 | 
211 |         wait = (random.random())**5 * 9 + 1  # 停1~9秒 指数越高平均间隔越短
212 |         print('---\nWait %.3f s...' % wait)
213 |         time.sleep(wait)
214 |         print('Continue!')
215 | 
216 | # ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
217 | 
218 | 
219 | if __name__ == '__main__':
220 |     try:
221 |         main()
222 |     except KeyboardInterrupt:
223 |         os.system('adb kill-server')
224 |         print('bye')
225 |         exit(0)
226 | 


--------------------------------------------------------------------------------
/wechat_jump_iOS_py3.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | import time
 3 | import wda
 4 | import numpy as np
 5 | import matplotlib.pyplot as plt
 6 | import matplotlib.animation as animation
 7 | from PIL import Image
 8 | 
 9 | # 截图距离 * time_coefficient = 按键时长
10 | # time_coefficient:
11 | #    iphonex: 0.00125
12 | #    iphone6: 0.00196
13 | #    iphone6s plus: 0.00120
14 | time_coefficient = 0.00120
15 | VERSION = "1.1.4"
16 | 
17 | c = wda.Client()
18 | s = c.session()
19 | 
20 | 
21 | def pull_screenshot():
22 |     c.screenshot('autojump.png')
23 | 
24 | 
25 | def jump(distance):
26 |     press_time = distance * time_coefficient
27 |     press_time = press_time
28 |     print('press_time = ',press_time)
29 |     s.tap_hold(200, 200, press_time)
30 | 
31 | 
32 | fig = plt.figure()
33 | pull_screenshot()
34 | img = np.array(Image.open('autojump.png'))
35 | im = plt.imshow(img, animated=True)
36 | 
37 | update = True
38 | click_count = 0
39 | cor = []
40 | 
41 | 
42 | def update_data():
43 |     return np.array(Image.open('autojump.png'))
44 | 
45 | 
46 | def updatefig(*args):
47 |     global update
48 |     if update:
49 |         time.sleep(1)
50 |         pull_screenshot()
51 |         im.set_array(update_data())
52 |         update = False
53 |     return im,
54 | 
55 | 
56 | def on_click(event):
57 |     global update
58 |     global ix, iy
59 |     global click_count
60 |     global cor
61 | 
62 |     ix, iy = event.xdata, event.ydata
63 |     coords = [(ix, iy)]
64 |     print('now = ', coords)
65 |     cor.append(coords)
66 | 
67 |     click_count += 1
68 |     if click_count > 1:
69 |         click_count = 0
70 |         cor1 = cor.pop()
71 |         cor2 = cor.pop()
72 | 
73 |         distance = (cor1[0][0] - cor2[0][0])**2 + (cor1[0][1] - cor2[0][1])**2
74 |         distance = distance ** 0.5
75 |         print('distance = ', distance)
76 |         jump(distance)
77 |         update = True
78 | 
79 | 
80 | fig.canvas.mpl_connect('button_press_event', on_click)
81 | ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True)
82 | plt.show()
83 | 


--------------------------------------------------------------------------------
/wechat_jump_py3.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | import os
 3 | import time
 4 | import numpy as np
 5 | import matplotlib.pyplot as plt
 6 | import matplotlib.animation as animation
 7 | from PIL import Image
 8 | 
 9 | VERSION = "1.1.4"
10 | def pull_screenshot():
11 |     os.system('adb shell screencap -p /sdcard/autojump.png')
12 |     os.system('adb pull /sdcard/autojump.png .')
13 | 
14 | 
15 | def jump(distance):
16 |     press_time = distance * 1.35
17 |     press_time = int(press_time)
18 |     cmd = 'adb shell input swipe 320 410 320 410 ' + str(press_time)
19 |     print(cmd)
20 |     os.system(cmd)
21 | 
22 | 
23 | fig = plt.figure()
24 | pull_screenshot()
25 | img = np.array(Image.open('autojump.png'))
26 | im = plt.imshow(img, animated=True)
27 | 
28 | update = True
29 | click_count = 0
30 | cor = []
31 | 
32 | 
33 | def update_data():
34 |     return np.array(Image.open('autojump.png'))
35 | 
36 | 
37 | def updatefig(*args):
38 |     global update
39 |     if update:
40 |         time.sleep(1.5)
41 |         pull_screenshot()
42 |         im.set_array(update_data())
43 |         update = False
44 |     return im,
45 | 
46 | 
47 | def on_click(event):
48 |     global update
49 |     global ix, iy
50 |     global click_count
51 |     global cor
52 | 
53 |     ix, iy = event.xdata, event.ydata
54 |     coords = [(ix, iy)]
55 |     print('now = ', coords)
56 |     cor.append(coords)
57 | 
58 |     click_count += 1
59 |     if click_count > 1:
60 |         click_count = 0
61 |         cor1 = cor.pop()
62 |         cor2 = cor.pop()
63 | 
64 |         distance = (cor1[0][0] - cor2[0][0])**2 + (cor1[0][1] - cor2[0][1])**2
65 |         distance = distance ** 0.5
66 |         print('distance = ', distance)
67 |         jump(distance)
68 |         update = True
69 | 
70 | 
71 | fig.canvas.mpl_connect('button_press_event', on_click)
72 | ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True)
73 | plt.show()
74 | 


--------------------------------------------------------------------------------