技术探讨微信跳一跳自动实现(C++版)。


免责声明:请勿用于刷分,由此导致的分数被屏蔽、微信被封号后果自负。

近来微信跳一跳很火,手动玩的最高纪录才150分,而最常见的情况是10分以内就挂,由此萌生了是否能技术实现的想法。当然,对刷分并不感兴趣,只是想学习下各位大佬们的代码。知乎上这个话题已经炸开了锅,python版满天飞,这里是一个C++实现。(物理外挂简直丧心病狂(〝▼皿▼))

适用范围:Android

原理

借助adb

电脑通过 adb 与Android手机互联。
为了捕捉屏幕信息,需要截图:adb shell screencap -p 图片路径
借助adb pull 图片路径提交到本地。

1
2
3
4
5
void ADBHelper::CapScreen()
{
system("adb shell screencap -p /sdcard/JumpByJump.png");
system("adb pull /sdcard/JumpByJump.png");
}

模拟滑动事件adb shell input swipe x1 y1 x2 y2 time,即从点[x1,y1]滑动到点[x2,y2],以及滑动时间(或者说是这儿的触摸时间)。这里我们不需要滑动到别处,因此可设置定点。

1
2
3
4
5
6
void ADBHelper::jump(int pressTime)
{
char cmd[256] = { 0 };
sprintf_s(cmd, "adb shell input swipe 320 410 320 410 %d", pressTime);
system(cmd);
}

计算距离

根据勾股定理,计算棋子目前所处位置到下一点(方块中心)的距离。所以就需要考虑如何获取当前位置和目标点坐标。这也是该技术探讨的核心所在。这里用到了OpenCV库来识别位置。(当初OpenCV、OpenGL傻傻分不清)

最傻瓜

手动点击棋子位置和目标位置,获取坐标。

优点:思路简单,准确度还可以。
缺点:操作麻烦,需手动跟进目标,自动化程度低。

稍改进

自动获取棋子位置,利用棋子颜色和方块较大的色差来做文章是很多帖子的思路。
然后手动点击目标位置。

较上述方案点击次数减少一半,但依然麻烦。

比较好

利用图像处理知识,自动获取两点坐标。
如果想自动获取坐标,可以看到,立方体的性状是正方体和圆柱居多,立方体只需记录最上方和最右方的点的位置,取最上方的横坐标和最右方的纵坐标即得中心点坐标。
其它很多算法下方链接已附。

AI

神经网络截取样本进行训练,然后让其自动跳跳跳。

暂时只实现了前2种……

设置参数

可以发现触摸时间越长,跳动距离越远。最简单的假设就是线性关系。distance=timek=distance(1/k),令1/k=parameter,即所需参数。我们经测试显示,parameter值在本程序和手机环境(分辨率1920×1080)中1.15比较合适。通过设置参数,计算出触摸时间。

代码流程

代码主体建立在参考里的jump1jump项目(借用了adb、cv库和相关的dll),进行了一些修改。

入口

1
2
3
4
5
6
int main(int argc, char* argv[])
{
JumpByJump jumper;
jumper.CarryOut();
return 0;
}

JumpByJump是主要的处理类,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class JumpByJump
{
public:
JumpByJump();
~JumpByJump();

double m_parameter;
void CarryOut();//执行操作
void ReadImage();//读取截图
CvPoint LocateChess();//棋子定位
void ShowScreen();//刷新屏幕
double m_targetPosX;
double m_targetPosY;
double m_sourcePosX;
double m_sourcePosY;
private:
IplImage* m_image;
IplImage* m_imgChessModel;
IplImage* m_matchResult;
};

第一次鼠标点击回调函数,获取棋子坐标->第一次鼠标点击回调函数,获取目标点坐标->计算两点距离->调用adb模拟触摸跳跃->等待截图->捕捉屏幕->读取截图->显示在电脑屏幕上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void onClick(int event, int x, int y, int flags, void* param)
{
if (event != CV_EVENT_LBUTTONUP)
{
return;
}
JumpByJump* jumper = (JumpByJump*)param;

if (m_iCount % 2 == 0)
{

cout << "棋子坐标:" << "(" << x << "," << y << ")" << endl;
jumper->m_sourcePosX = x;
jumper->m_sourcePosY = y;
m_iCount++;
}
else
{
cout << "目标点坐标:" << "(" << x << "," << y << ")" << endl;
jumper->m_targetPosX = x;
jumper->m_targetPosY = y;
// CvPoint chessLoc = jumper->LocateChess();
double distance = sqrt(powf(jumper->m_targetPosX - jumper->m_sourcePosX, 2) + powf(jumper->m_targetPosY - jumper->m_sourcePosY, 2));
ADBHelper::jump(distance * m_parameter);
Sleep(1000);//等待截图
ADBHelper::CapScreen();
jumper->ReadImage();
jumper->ShowScreen();
m_iCount++;
}
}

执行函数:捕捉屏幕->读取截图->获取棋子坐标->电脑显示模拟画面->刷新画面

1
2
3
4
5
6
7
void JumpByJump::CarryOut()
{
ADBHelper::CapScreen();
ReadImage();
UpdateScreen();
cvWaitKey(0);
}

cvWaitKey()函数的功能是不断刷新图像,频率时间为delay,单位为ms。

定位两点位置是核心关键。(手动设置两点则无需该函数)

1
2
3
4
5
6
7
8
9
10
11
12
CvPoint JumpByJump::LocateChess()
{
cvMatchTemplate(m_image, m_imgChessModel, m_matchResult, CV_TM_SQDIFF);
double min_val = 0, max_val = 0;
CvPoint min_loc, max_loc, chessPt;
cvMinMaxLoc(m_matchResult, &min_val, &max_val, &min_loc, &max_loc);
cvRectangle(m_image, min_loc, cvPoint(min_loc.x + m_imgChessModel->width, min_loc.y + m_imgChessModel->height), cvScalar(0, 0, 255), 2);
cout << "棋子当前坐标:" << "(" << min_loc.x << "," << min_loc.y<<")" << endl;

chessPt = cvPoint(min_loc.x + m_imgChessModel->width / 2, min_loc.y + m_imgChessModel->height);
return chessPt;
}

opencv 模板匹配(cvMatchTemplate)
模板匹配是通过在输入图像上滑动模板图像块对实际的图像块和输入图像进行匹配,并且可以利用函数cvMinMaxLoc()找到最佳匹配的位置。

示例

手动2点版

控制台
控制台

成绩
成绩

(最后点的时候手滑了…正常几百上千应该可以的)

下载

所有工程文件和代码可在此处下载:JumpByJump

参考

jump1jump
人人都能看懂的“跳一跳”平民算法

附知乎热帖:怎样实现微信小游戏跳一跳的外挂?