跳一跳 python脚本 改进版

时间:2021-08-09 04:16:58

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

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

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

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

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


jump.py

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

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


VERSION = "1.1.2"

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

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


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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

return end_x, end_y, start_x, start_y


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


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

# debug.dump_device_info()
screenshot.check_screenshot()

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

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


if __name__ == '__main__':
main()

游戏效果图如下:

跳一跳 python脚本 改进版

然而,,,

跳一跳 python脚本 改进版

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