一、前言:
前段时间微信更新了新版本后,带来的一款H5小游戏“跳一跳”在各朋友圈里又火了起来,类似以前的“打飞机”游戏,这游戏玩法简单,但加上了积分排名功能后,却成了“装逼”的地方,于是很多人花钱花时间的刷积分抢排名。后来越来越多的聪明的“程序哥们”弄出了不同方式不同花样的跳一跳助手(外挂?),有用JS实现的、有JAVA实现的、有Python实现的,有直接物理模式的、有机械化的、有量尺子的等等,简直是百花齐放啊……
赶一下潮流,刚好有点时间,于是花了一个下午时间,我也弄了一个C#版本的简单实现。
二、实现:
简单的实现流程: 连接手机 -> 获取跳一跳游戏界面 -> 获取位置(棋子位置和要跳跃的落脚点位置) -> 点击棋子跳跃
1、连接手机
电脑要连接并操作安卓手机,一般是通过ADB协议连接手机并进行操作。连接手机前要求手机已开启USB调试模式,可通过USB线或者TCP方式连接手机。正常只要电脑安装了adb sdk tools之类的工具包,就会自带有adb命令,所以C#要能操作手机,简单实现就是直接利用现成的adb命令。
手机通过USB线接入电脑后,在CMD窗口输入以下adb devices命令,如果显示有device列表则表示手机已连接成功可以对手机进行操作了。
C:\Users\k>adb devices List of devices attached e832acb device
2、获取游戏界面
获取手机界面的截图可通过以下adb命令获取:
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)); }注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。