最近微信上线了一款小应用—“跳一跳”,这个规则简单,让人上瘾的小游戏和2048一样魔性,朋友圈也是各路小伙伴各显神通:硬件流(树莓派+步进电机)、日天流(篡改http请求)、软件流(adb控制手机模拟点击)。
今天我们也来实践下,当然选择最顺手的Python来搞咯,直接找到开源项目wechat_jump_game进行优化改造。此项目有个pull request[优化]跑分17000+ 新增AI模块,机器人自主学习生成跳跃公式,看到AI我们就来了兴趣,只见过理论,还没有实践过,可以拿这个实践下。
这个pull request介绍如下:
机器人精确采集跳跃结果并自主学习,使用线性回归方法
拟合出最优 [按压时间]->[弹跳距离] 线性公式 Y =kX + b
本优化无需修改config文件,可以适配所有手机,经过十次以上跳跃学习,机器人即可
模拟出相对稳定的线性公式。随着采集结果越多,跳跃也越精确,后期基本连续命中靶心。
理论上只要目标点获取无误,会一直跳下去。
工作两年多,一直在做服务端后台应用相关的测试,没接触过移动端测试呢,正好趁这次机会学习下怎么通过代码自动化控制安卓手机。
下面来动手试一下,找出下岗多年的MX3,充电开机。
第一次调试
- 安装好adb,配置好环境变量。
- 手机打开开发者模式,连接PC。
- 命令行测试是否连接成功: adb devices,手机弹出是否信任窗口,点击确定,已经链接成功。
- 测试一些adb命令是否正常: adb shell wm size,返回信息:Physical size: 1080x1800,完美。
- 通过virtualenv建立虚拟环境,安装项目所需的第三方库。
- 手机微信打开跳一跳,点击开始游戏。
- 运行wechat_jump_auto_ai.py,报错T_T…
查看代码发现是截图部分操作不适配MX3,手动修改代码后成功截图运行
将修改为1
screenshot = screenshot.replace(b'\r\n', b'\n')
1
screenshot = screenshot.replace(b'\r\r\n', b'\n')
第二次调试
按照程序逻辑,运行十次之后即可采用线性回归算法学习得到的公式,根据已知距离得出按压时间,但实际结果却和一个弱智一样,2分就挂掉了…
查看代码发现有个魔法数字要自己设置,程序根据这个数字进行截图计算误差:time.sleep(0.2)。
1 | # 在跳跃落下的瞬间 摄像机移动前截图 这个参数要自己校调 |
2 | time.sleep(0.2) |
3 | pull_screenshot_temp() |
4 | im_temp = Image.open('./autojump_temp.png') |
5 | temp_piece_x, temp_piece_y = find_piece(im_temp) |
6 | debug.computing_error(press_time, board_x, board_y, piece_x, piece_y, temp_piece_x, temp_piece_y) |
经过debug截图不断调整,得出我的PC和MX3配合的最佳数值是0.04。
删除学习数据jump_range.csv,重新开始训练,程序终于能磕磕绊绊达到400分左右,但是大概需要1小时左右,对学习数据通过pandas和matplotlib进行绘图,看到训练采集到的数据离散程度很高,明显学习效果不佳。
第三次调试(重点)
再次阅读代码,发现这个AI版本的代码有瑕疵:
1 | def computing_error(last_press_time, target_board_x, target_board_y, last_piece_x, last_piece_y, temp_piece_x, temp_piece_y): |
2 | """ |
3 | 计算跳跃实际误差 |
4 | """ |
5 | target_distance = math.sqrt(abs(target_board_x - last_piece_x) ** 2 + abs(target_board_y - last_piece_y) ** 2) # 上一轮目标跳跃距离 |
6 | actual_distance = math.sqrt(abs(temp_piece_x - last_piece_x) ** 2 + abs(temp_piece_y - last_piece_y) ** 2) # 上一轮实际跳跃距离 |
7 | jump_error_value = math.sqrt(abs(target_board_x - temp_piece_x) ** 2 + abs(target_board_y - temp_piece_y) ** 2) # 跳跃误差 |
8 | |
9 | print "目标距离: {0}, 实际距离: {1}, 误差距离: {2}, 蓄力时间: {3}ms".format(round(target_distance), round(actual_distance), round(jump_error_value), round(last_press_time)) |
10 | # 将结果采集进学习字典 |
11 | |
12 | if last_piece_x > 0 and last_press_time > 0 : |
13 | ai.add_data(round(actual_distance, 2), round(last_press_time)) |
问题1
target_distance与actual_distance有可能会等于0.0或者一个超出实际范围的值,这些值会在游戏失败重新开始时出现,原程序没有进行过滤;
问题2
jump_error_value作为一个很重要的值,没有进行有效利用。
上述两个问题会导致数据质量不高,干扰项太多,目标函数拟合速度太慢。
因此改进数据采集策略:
1 | if jump_error_value < 5 and last_piece_x > 0 and last_press_time > 0 and target_distance > 0 and actual_distance > 0: |
2 | ai.add_data(round(actual_distance, 2), round(last_press_time)) |
改进1
要求target_distance与actual_distance必须要大于0才是有效值,剔除干扰项;
改进2
jump_error_value合理利用,开始时将此参数放大到50左右,后续根据学习效果,逐步缩小jump_error_value的阈值,提高精确度,加速训练效果。
效果展示
训练数据拟合函数
经过大概四五轮训练,采样到571条数据,拟合出线性回归函数如图
debug误差效果
运行debug截图, 基本完美命中目标
运行debug日志,误差基本控制在40以内
自动运行视频
成绩
因为MX3手机硬件有问题,电量低于75%就会出现屏幕抖动,所以目前成绩如此
项目地址
优化后的代码以及训练数据: https://github.com/toddlerya/WechatJumpAI
后记
不得不承认,机器学习在合适的领域使用合适方式,达到的效果非常棒!只用了简单的线性回归算法,最经典最基本的机器学习算法,达到的效果已经秒杀人类上千倍。
为了不被这个时代淘汰,一定要跟上节奏,加油~