[原创]用C#实现微信“跳一跳”小游戏的自动跳跃助手

简介:

一、前言:

前段时间微信更新了新版本后,带来的一款H5小游戏“跳一跳”在各朋友圈里又火了起来,类似以前的“打飞机”游戏,这游戏玩法简单,但加上了积分排名功能后,却成了“装逼”的地方,于是很多人花钱花时间的刷积分抢排名。后来越来越多的聪明的“程序哥们”弄出了不同方式不同花样的跳一跳助手(外挂?),有用JS实现的、有JAVA实现的、有Python实现的,有直接物理模式的、有机械化的、有量尺子的等等,简直是百花齐放啊……

赶一下潮流,刚好有点时间,于是花了一个下午时间,我也弄了一个C#版本的简单实现。

 

二、实现:

简单的实现流程: 连接手机 -> 获取跳一跳游戏界面 -> 获取位置(棋子位置和要跳跃的落脚点位置) -> 点击棋子跳跃

1、连接手机

电脑要连接并操作安卓手机,一般是通过ADB协议连接手机并进行操作。连接手机前要求手机已开启USB调试模式,可通过USB线或者TCP方式连接手机。正常只要电脑安装了adb sdk tools之类的工具包,就会自带有adb命令,所以C#要能操作手机,简单实现就是直接利用现成的adb命令。

手机通过USB线接入电脑后,在CMD窗口输入以下adb devices命令,如果显示有device列表则表示手机已连接成功可以对手机进行操作了。

1
2
3
C:\Users\k>adb devices
List of devices attached
e832acb device

  

2、获取游戏界面

获取手机界面的截图可通过以下adb命令获取:

1
adb shell screencap -p [filename] 

参数 :

- p 表示截图保存格式为PNG图像格式。

filename: 截图保存的路径地址(手机路径),如果不输入则将截图数据直接输出到当前控制台会话,否则会将截图保存到相关路径地址(必须有写权限)

为避免文件保存到手机后还要再执行adb pull(拉文件到本地电脑)的操作,所以选择不带filename参数的命令。在C#代码里通过Process这个类进行adb命令的调用执行,实现代码如下:

复制代码
var startInfo = new ProcessStartInfo("adb", "shell screencap -p");
startInfo.CreateNoWindow = true;
startInfo.ErrorDialog = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
var process = Process.Start(startInfo);
process.Start();
var memoStream = new MemoryStream();
process.StandardOutput.BaseStream.CopyTo(memoStream);
复制代码

 

但由于adb client的原因,在它输出的截图数据流中会对'\n'(0A)这个字符替换为''\r\n'(0D0A)这两个字符,并且在测试中还发现不同的手机替换次数还不相同的,有可能替换一次,也有可能替换二次!所以为解决这个问题,先计算在最开始的10字节里的数据出现了多少次'\r'(0D)字符后再出现‘\n'(0A)字符,因为正常的PNG文件,在文件头的第4,第5个字节位置里会有'\r\n'(0D0A)标志,所以检查出来的出现次数就表示'\n'(0A)被adb client替换了多少次,之后再对整个接收到的数据流进行'\n'(0A)还原(删除无用的'\r'(0D)字符)。

>>统计'\n'被替换了次

复制代码
        private static int Find0DCount(MemoryStream stream)
        {
            int count = 0;
            stream.Position = 0;
            while(stream.Position < 10 && stream.Position < stream.Length)
            {
                int b = stream.ReadByte();
                if(b == '\r')
                {
                    count++;
                }
                else if(b == '\n')
                {
                    return count;
                }else if(count > 0)
                {
                    count = 0;
                }
            }
            return 0;
        }
复制代码

 

>>对接受到的截图数据流进行'\n'字符还原

复制代码
                var count = Find0DCount(memoStream);

                var newStream = new MemoryStream();
                memoStream.Position = 0;
                while (memoStream.Position != memoStream.Length)
                {
                    var b = memoStream.ReadByte();
                    if (b == '\r')
                    {
                        int c = 1;
                        var b1 = memoStream.ReadByte();
                        while(b1 == '\r' && memoStream.Position != memoStream.Length)
                        {
                            c++;
                            b1 = memoStream.ReadByte();
                        }
                        if(b1 == '\n')
                        {
                            if(c == count)
                            {
                                newStream.WriteByte((byte)'\r');
                            }
                            newStream.WriteByte((byte)b1);
                        }
                        else
                        {
                            for(int i=0; i<c; i++) newStream.WriteByte((byte)'\r');
                            newStream.WriteByte((byte)b1);
                        }
                    }
                    else { 
                        newStream.WriteByte((byte)b);
                    }
                }

                return new Bitmap(newStream);
复制代码

 

3、获取棋子与跳跃落脚点位置

将获取到的手机界面截图显示到软件窗体上的PictureBox控件上,可用鼠标的左右键分别点击图片位置标示棋子位置和需要跳的落脚点位置,鼠标点击的坐标位置即表示手机界面的坐标位置。由于手机界面截图在PictureBox控件显示时为了能一屏全图显示,对图片做了缩放处理,且图片缩放后如果图片的宽度小于PictureBox控件的宽度,PictureBox会将图片居中后显示。所以鼠标点击的坐标位置还需要进行坐标转换才可以映射为手机界面里的绝对坐标位置。

转换计算方法:先计算PictureBox控件的图片缩放值和图片显示的左边距,然后再对鼠标点击坐标进行缩放计算。代码如下:

复制代码
        private Point CalPoint(Point p)
        {
            if (this.cbZoom.Checked && this.pictureBox1.Image != null)
            {
                var zoom = (double)this.pictureBox1.Height / this.pictureBox1.Image.Height;
                var width = (int)(this.pictureBox1.Image.Width * zoom);
                var left = this.pictureBox1.Width / 2 - width / 2;
                return new Point((int)((p.X - left) / zoom), (int)(p.Y / zoom));
            }
            else
            {
                return p;
            }
        }
复制代码

 

如全靠手动鼠标点击坐标位置来玩游戏,这和直接在手机里手动玩游戏是没有什么区别的,区别只在于能够跳跃精准些(跳跃力度能自动计算出,下面会讲),所以程序还要能够实现自动化,就是要能够自动找出棋子与跳跃落脚点的位置。

A、找棋子的坐标位置

棋子的位置非常的好找,对游戏界面里的棋子(图2黄色块)进行放大可以发现棋子底部有一块区域(图3白色块)的颜色值是固定的R(54)G(60)B(102)颜色,如下两图:

(图2)

(图3)

 

根据棋子的这一颜色特点在获取到手机界面截图时,对图片象素进行扫描,查找R(54)G(60)B(102)这一颜色,找到的坐标位置就是棋子的位置。为了能快速扫描图片,不采用效率较低下的GetPixel方法获取颜色值,而采用LockBits方法锁定图片数据到内存,再采用指针移动获取象素颜色,由于采用了指针,代码需要开启unsafe定义。且棋子正常情况下不会在最顶部和最底部出现,所以不需要对整张界面图片扫描,只扫描20%-63%区域的数据,并且从底部开始找起。

 

B、找跳跃的落脚点位置

写此助手只是无聊时的产出物,所以我只是简单实现。游戏中如果连续跳到了目标物的中间位置时,新目标物的中间部分会出现一个白色圈(如上图2的红色块),如果再跳中此位置,会进行加分。根据这一特点,程序找出那一白色圈圈的位置即可做为落脚点位置,白色圈的颜色值为R(254)G(254)B(254),如果没有此白色圈位置,则手动鼠标选择落脚点位置。实现此功能后,程序基本上也能实现90%左右的自动化跳跃了。

 

查找代码实现如下:

复制代码
private static Point FindPointImpl(Bitmap bitmap, out Point comboPoint)
        {
            var standPColor = Color.FromArgb(54, 60, 102);
            var comboPColor = Color.FromArgb(245, 245, 245);

            Point standPoint = Point.Empty;
            comboPoint = Point.Empty;

            int y1 = (int)(bitmap.Height * 0.2);
            int y2 = (int)(bitmap.Height * 0.63);

            PixelFormat pf = PixelFormat.Format24bppRgb;

            BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, y1, bitmap.Width, y2), ImageLockMode.ReadOnly, pf);
            try
            {
                unsafe
                {
                    int w = 0;
                    while (y2 > y1)
                    {
                        byte* p = (byte*)bitmapData.Scan0 + (y2 - y1 - 1) * bitmapData.Stride;
                        w = bitmap.Width;
                        int endColorCount = 0;
                        while (w > 40)
                        {
                            ICColor* pc = (ICColor*)(p + w * 3);
                            if (standPoint == Point.Empty &&
                                pc->R == standPColor.R && pc->G == standPColor.G && pc->B == standPColor.B)
                            {
                                standPoint = new Point(w - 3, y2);
                                if (comboPoint != Point.Empty) break;
                            }
                            else if (comboPoint == Point.Empty)
                            {
                                if (pc->R == comboPColor.R && pc->G == comboPColor.G && pc->B == comboPColor.B)
                                {
                                    endColorCount++;
                                }
                                else
                                {
                                    if (endColorCount > 0)
                                    {
                                        comboPoint = new Point(w + 5, y2 - 1);
                                        if (standPoint != Point.Empty) break;
                                    }
                                    endColorCount = 0;
                                }
                            }
                            w--;
                        }
                        if (comboPoint == Point.Empty)
                        {
                            if (endColorCount > 10)
                            {
                                comboPoint = new Point(w + 5, y2 - 1);
                            }
                        }
                        if (standPoint != Point.Empty && comboPoint != Point.Empty) break;
                        y2--;
                    }
                }
                return standPoint;
            }
            finally
            {
                bitmap.UnlockBits(bitmapData);
            }
        }
复制代码

 

 

4、棋子跳跃

要能跳跃,首先需要知道一个蓄力时间,就是按住棋子多久的时间,此蓄力时间的计算公式如下:

1
蓄力时间 = 距离 * 力度系数

  

距离”就是棋子位置与跳跃落脚点位置的距离,根据上面的方法得出这两个位置的坐标点后,根据直角三角形的勾股定理即可求出,代码如下:

复制代码
        public double Distance
        {
            get
            {
                if (!this.CanDo) return -1;
                int w = Math.Abs(this.P2.X - this.P1.X);
                int h = Math.Abs(this.P2.Y - this.P1.Y);
                return Math.Sqrt((double)(w * w) + (h * h)); } }
复制代码

力度系数”  是一个常量值,具体怎么定义没去细查,我采用的计算公式是: “力度系数 = 1495 / 手机分辨率的宽度值”, 如我的手机分辨率是1080*1920,则力度系数就是 1495 / 1080 = 1.3842....

算出了蓄力时间后通过以下adb命令发送到手机即可模拟点击操作。

1
adb shell input swipe <x1> <y1> <x2> <y2> [duration(ms)]

x1, y1 就是棋子的坐标位置

x2, y2 还是棋子的坐标位置

duration 蓄力时间值,由距离*力度系数得出。

 

代码如下:

复制代码
        public bool Do()
        {
            if (!this.CanDo) return false;

            var startInfo = new ProcessStartInfo("adb", string.Format("shell input swipe {0} {1} {0} {1} {2}", this.P1.X, this.P1.Y, this.Time));
            startInfo.CreateNoWindow = true;
            startInfo.ErrorDialog = true;
            startInfo.UseShellExecute = false;
            var process = Process.Start(startInfo);
            return process.Start();
        }
复制代码

 

三、结束语

程序实现很简单,都是通过adb命令与手机进行交互操作。如果你认为对你有帮助麻烦赞下即可:)积分别玩太过哦。

 

可执行文件下载地址:JumperHelper.rar

代码仓库:https://github.com/kingthy/JumperHelper

 

声明:本软件、代码和文章属于本人原创,转载请通知并注明原处!

本文转自Kingthy博客园博客,原文链接:http://www.cnblogs.com/kingthy/p/jumperhelper.html ,如需转载请自行联系原作者
相关实践学习
使用CLup和iSCSI共享盘快速体验PolarDB for PostgtreSQL
在Clup云管控平台中快速体验创建与管理在iSCSI共享盘上的PolarDB for PostgtreSQL。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
相关文章
C#窗体连连看小游戏(超详细)(下)
C#窗体连连看小游戏(超详细)
280 0
|
3月前
|
小程序 前端开发 C#
C#微信公众号HIS预约挂号系统源码
微信公众号预约挂号系统、支付宝小程序预约挂号系统主要是让自费、医保患者在手机上就能实现就医全过程,实时预约挂号、自费、医保结算,同时还可以查询检查检验报告等就诊信息,真正实现了让信息“多跑路”,让群众“少跑腿”。系统与HIS对接,通过医院微信公众号,患者用身份证注册以后,可以预约看诊的时间、医生挂号缴费。预约成功后,会收到预约码或二维码,患者可以在预约的时间段,前往医院看诊。既可以节约患者的等待时间,又可以降低医院的负荷。 一、开发环境 ❀技术架构:net     ❀开发语言:C# ❀开发工具:VS2019     ❀前端框架:uni-app     ❀后端框架:net     ❀数 据
88 1
|
7月前
|
小程序 算法 API
可能是全网最完整的 C# 版微信 SDK
可能是全网最完整的 C# 版微信 SDK
172 0
|
算法 C#
C#窗体连连看小游戏(超详细)(上)
C#窗体连连看小游戏(超详细)
293 0
C#窗体连连看小游戏(超详细)(上)
|
运维 机器人 Java
Springboot 整合 企业微信机器人助手推送消息
Springboot 整合 企业微信机器人助手推送消息
898 0
Springboot 整合 企业微信机器人助手推送消息
微信数据小助手
微信数据小助手
165 0
C#小游戏之疯狂字母
C#小游戏之疯狂字母
67 0
C#小游戏之疯狂字母
儿童节教大家做一个C#图片匹配小游戏
儿童节教大家做一个C#图片匹配小游戏
123 0
儿童节教大家做一个C#图片匹配小游戏
|
C#
C# 实现生成带二维码的专属微信公众号推广海报
原文:C# 实现生成带二维码的专属微信公众号推广海报 很多微信公众号中需要生成推广海报的功能,粉丝获得专属海报后可以分享到朋友圈或发给朋友,为公众号代言邀请好友即可获取奖励的。海报自带渠道二维码,粉丝长按二维码即可关注微信公众号,从而达到吸粉的目的。
2320 0
|
存储 监控 C#
基于C#简单实现的微信网页版接口
曾经,碎碎念的烦弃微信各种功能,无法拒绝语音消息,讨厌的微商,讨厌的领导,蛋疼的界面
3289 0