微信跳一跳自动辅助

最近微信跳一跳在元旦一周火了起来, 刚玩的时候能艰难的跳到120+分, 看了网上的站立加分能跳到280+. 使用GGuardian让每次踩中心能加32分能跳到接近1000, 然而每次重开太累了, 于是抽晚上和周末写了个自动跳. 目前最高分有4000+, 不过是因为要玩手机而手动停止, 并不是算法问题. 想直接使用的看这里

思路

  1. 手机截图发送到电脑
  2. 电脑读取图片像素, 分析图片, 得到跳跃距离
  3. 根据跳跃距离和计算函数得出按压时间
  4. 发送按压时间到手机长按 -> 跳!
  5. 再次截图, 并循环

手机截图

还好使用的是android, 可以直接使用adb shell进行截图, 并发送到电脑. 百度一下可以得到以下几个命令.

1
2
3
4
//调用截图, 并将截图保存到/sdcard/screenshot.png
adb shell /system/bin/screencap -p /sdcard/screenshot.png
//将截图从手机复制到电脑目录
adb pull /sdcard/screenshot.png d:/screenshot.png

然后再带一个删除图片的linux命令, 免得截图太多浪费内存空间.

1
adb shell rm /sdcard/screenshot.png

按压命令

顺便查了一下发送输入事件到手机的命令, 也挺简单的.

1
2
3
4
//点击x,y坐标
adb shell input tap x y
//使用n毫秒从(x1,y1)滑动到(x2,y2), 模拟长按事件
adb shell input swipe 500 500 501 501 2000

测试了一下, 发现一般距离按个300ms-400ms, 基本都能跳到. 算测试成功, 进行下一步.

开始

帮助类库

首先还是使用.Net + C#, 毕竟window平台下最好用的语言. 图片的读取和写入还是使用之前读取支付宝 AR 红包时候用的Byte[]System.Runtime.InteropServices.Marshal, 性能远远优于.Net自带的图片像素颜色读取写入. 方法也类似之前的方法, 只是数组改成二维数组了, 方便一些, 由于有半透明图片的读取, 所以还添加了一个读取RGBA值的方法.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public static void GetRGB(this Bitmap image, int startX, int startY, int w, int h, int[][] rgbArray)
{
const int PixelWidth = 3;
const PixelFormat PixelFormat = PixelFormat.Format24bppRgb;

// En garde!
if (image == null) throw new ArgumentNullException("image");
if (rgbArray == null) throw new ArgumentNullException("rgbArray");
if (startX < 0 || startX + w > image.Width) throw new ArgumentOutOfRangeException("startX");
if (startY < 0 || startY + h > image.Height) throw new ArgumentOutOfRangeException("startY");
if (w < 0 || w > image.Width) throw new ArgumentOutOfRangeException("w");
if (h < 0 || (rgbArray.Length < h) || h > image.Height) throw new ArgumentOutOfRangeException("h");

BitmapData data = image.LockBits(new Rectangle(startX, startY, w, h), System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat);
try
{
byte[] pixelData = new Byte[data.Stride];
for (int i = 0; i < h; i++)
{
rgbArray[i] = new int[w];
Marshal.Copy(data.Scan0 + (i * data.Stride), pixelData, 0, data.Stride);
for (int j = 0; j < w; j++)
{
// PixelFormat.Format32bppRgb means the data is stored
// in memory as BGR. We want RGB, so we must do some
// bit-shuffling.
rgbArray[i][j] =
(pixelData[j * PixelWidth + 2] << 16) + // R
(pixelData[j * PixelWidth + 1] << 8) + // G
pixelData[j * PixelWidth]; // B
}
}
}
finally
{
image.UnlockBits(data);
}
}
public static void GetRGBA(this Bitmap image, int startX, int startY, int w, int h, long[][] rgbArray)
{
const int PixelWidth = 4;
const PixelFormat PixelFormat = PixelFormat.Format32bppArgb;

// En garde!
if (image == null) throw new ArgumentNullException("image");
if (rgbArray == null) throw new ArgumentNullException("rgbArray");
if (startX < 0 || startX + w > image.Width) throw new ArgumentOutOfRangeException("startX");
if (startY < 0 || startY + h > image.Height) throw new ArgumentOutOfRangeException("startY");
if (w < 0 || w > image.Width) throw new ArgumentOutOfRangeException("w");
if (h < 0 || (rgbArray.Length < h) || h > image.Height) throw new ArgumentOutOfRangeException("h");

BitmapData data = image.LockBits(new Rectangle(startX, startY, w, h), System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat);
try
{
byte[] pixelData = new Byte[data.Stride];
for (int i = 0; i < h; i++)
{
rgbArray[i] = new long[w];
Marshal.Copy(data.Scan0 + (i * data.Stride), pixelData, 0, data.Stride);
for (int j = 0; j < w; j++)
{
// PixelFormat.Format32bppARgb means the data is stored
// in memory as BGRA. We want ARGB, so we must do some
// bit-shuffling.
rgbArray[i][j] =
(((long)pixelData[j * PixelWidth + 3]) << 24) + // A
(pixelData[j * PixelWidth + 2] << 16) + // R
(pixelData[j * PixelWidth + 1] << 8) + // G
pixelData[j * PixelWidth]; // B
}
}
}
finally
{
image.UnlockBits(data);
}
}
public static void SetRGB(this Bitmap image, int startX, int startY, int w, int h, int[][] rgbArray)
{
const int PixelWidth = 3;
const PixelFormat PixelFormat = PixelFormat.Format24bppRgb;

// En garde!
if (image == null) throw new ArgumentNullException("image");
if (rgbArray == null) throw new ArgumentNullException("rgbArray");
if (startX < 0 || startX + w > image.Width) throw new ArgumentOutOfRangeException("startX");
if (startY < 0 || startY + h > image.Height) throw new ArgumentOutOfRangeException("startY");
if (w < 0 || w > image.Width) throw new ArgumentOutOfRangeException("w");
if (h < 0 || (rgbArray.Length < h) || h > image.Height) throw new ArgumentOutOfRangeException("h");

BitmapData data = image.LockBits(new Rectangle(startX, startY, w, h), System.Drawing.Imaging.ImageLockMode.WriteOnly, PixelFormat);
try
{
int dst_bytes = data.Stride * h;
byte[] dstValues = new byte[dst_bytes];
IntPtr dstPtr = data.Scan0;
Marshal.Copy(dstPtr, dstValues, 0, dst_bytes);
int position = 0;

for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
position = (i * data.Stride) + j * PixelWidth;
dstValues[position + 0] = (byte)(rgbArray[i][j]);
dstValues[position + 1] = (byte)(rgbArray[i][j] >> 8);
dstValues[position + 2] = (byte)(rgbArray[i][j] >> 16);
}
}
Marshal.Copy(dstValues, 0, dstPtr, dst_bytes);
}
finally
{
image.UnlockBits(data);
}
}

开始的想法是, 从左上往右下扫描图片, 当碰到顶点的时候, 就能确定该顶点是目标物体, 然后根据顶点颜色找到整个目标面, 计算下平均坐标, 即可得到目标点.
而自己的坐标确认方法, 看了一下好像自己的颜色不会变化, 那么就去PS里面, 把自己的棋子单独截出来, 当作Res资源图片, 在扫描时候碰见自己棋子颜色的时候, 就去和资源图片比对, 如果相关像素点颜色都一样, 那么就可以确定该点为自己的棋子, 再根据起点坐标和资源图片计算偏移, 即可得到自己的准确坐标.

那么就开始干活. 首先定义一个结构struct记录点的颜色, 为什么不是class而是struct呢, 因为性能好一些, 而且不会有引用值类型传错的可能. .Net还有个好处就是和C一样, 能重载+, ‘-‘, ‘*‘, ‘/‘, ‘==’这种运算符, 这样算法写起来也简单.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
public struct RGB
{
public static char[] RGBNAME = new char[] { 'A', 'R', 'G', 'B' };

public RGB(long color, bool hasAlpha = false)
{
//this.R = color >> 16;
//this.G = (color & 65280) >> 8;
//this.B = (color & 255);
this.A = hasAlpha ? (byte)(color >> 24) : 255;
this.R = (byte)(color >> 16);
this.G = (byte)(color >> 8);
this.B = (byte)(color);
}
public static RGB FromInt(long color, bool hasAlpha = false)
{
return new RGB(color, hasAlpha);
}
public int ToInt()
{
return (this.R << 16) + (this.G << 8) + this.B;
}
public long ToLong(bool hasAlpha = false)
{
return (hasAlpha ? ((long)this.A) << 24 : 0) + (this.R << 16) + (this.G << 8) + this.B;
}
public RGB(int r, int g, int b)
{
this.R = r;
this.G = g;
this.B = b;
this.A = 255;
}
public RGB(int r, int g, int b, int a)
{
this.R = r;
this.G = g;
this.B = b;
this.A = a;
}
public int Get(char key)
{
if (key > 90)
{
key = (char)(key - 32);
}
switch (key)
{
case 'A': return this.A;
case 'R': return this.R;
case 'G': return this.G;
case 'B': return this.B;
}
throw new ArgumentOutOfRangeException("字符不对");
}
public static RGB operator +(RGB color1, RGB color2)
{
return new RGB(
color1.R + color2.R,
color1.G + color2.G,
color1.B + color2.B
);
}
public static RGB operator -(RGB color1, RGB color2)
{
return new RGB(
color1.R - color2.R,
color1.G - color2.G,
color1.B - color2.B
);
}
public static RGB operator *(RGB color, int m)
{
return new RGB(
color.R * m,
color.G * m,
color.B * m
);
}
public static RGB operator /(RGB color, int m)
{
return new RGB(
color.R / m,
color.G / m,
color.B / m
);
}
public static RGB operator ~(RGB color)
{
return new RGB(
255 - color.R,
255 - color.G,
255 - color.B
);
}
public static bool operator ==(RGB color1, RGB color2)
{
return color1.A == color2.A && color1.R == color2.R && color1.G == color2.G && color1.B == color2.B;
}
public static bool operator !=(RGB color1, RGB color2)
{
return !(color1 == color2);
}
public int R;
public int G;
public int B;
public int A;
public override string ToString()
{
return string.Format("R:{0},G:{1},B:{2},A:{3}", this.R, this.G, this.B, this.A);
}
public override bool Equals(object obj)
{
if (obj != null && obj is RGB)
{
return this == (RGB)obj;
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return this.ToLong(true).GetHashCode();
}
}

有这个struct来帮助将颜色和bitmap里面存的int进行互相转换和计算以后, 就可以开始正式做图片识别相关的了, 目前还是使用的传统算法实现图片识别, 而不是网上比较多的人工智能Tensorflow + 训练集来识别图片. 两种算法各有优劣, 传统算法优势是意义明确, 可以清楚的知道代码是如何识别图片和如果失败哪里出现了问题, 人工智能识别的好处就是不用费心想算法…而且以后万一图片改了, 直接换个训练集一样能上能识别.

分析主函数

先定义分析主函数double AnalyseSpace(), 主要流程是从图片中读取颜色信息, 然后分析图片, 计算出目标点坐标和自己点坐标, 根据位置求两点距离.

1
2
3
4
5
6
7
8
9
10
11
12
13
var bitmap = (Bitmap)Image.FromFile(ImgBoxResolved.ImageLocation);
if (bitmap.Width != 1080 || bitmap.Height != 1920)
{
MessageBox.Show("图片错误");
return 0;
}
var imgRGBArray = new int[1920][];
bitmap.GetRGB(0, 0, 1080, 1920, imgRGBArray);

var chessPoint = GetMySelfChessPosition(imgRGBArray);
var targetPoint = FindTargetPoint(imgRGBArray);

return calcSpace(chessPoint, targetPoint);

计算目标点坐标

如何计算目标点坐标是个比较困难的问题, 最开始想的是扫描图片, 取最上方的节点颜色, 然后从该点开始取周围所有相同颜色的坐标, 最后求平均值就能得到中心点即目标点坐标了.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
private Point FindTargetPoint(int[][] imgRGBArray)
{
//从500开始逐行扫描
RGB tempRGB;
RGB tempBoxColorRGB = new RGB();
Point startPoint = new Point(0, 0);
Point endPoint = new Point(0, 0);
Point rightPoint = new Point(0, 0);
int rightHeight = 0;
int maybeHeight = 0;
var isFindStart = false;
var isInBox = true;
var isFindRight = false;
var findCount = 400;

var currentRowMaxJ = 0;

for (var i = 500; i < imgRGBArray.Length; i++)
{
currentRowMaxJ = 0;
for (var j = 0; j < imgRGBArray[i].Length; j++)
{
tempRGB = RGB.FromInt(imgRGBArray[i][j]);
if (!isFindStart)
{
if (!IsBgColor(tempRGB))
{
isFindStart = true;
startPoint.Y = i;
startPoint.X = j;
endPoint.X = j;
rightPoint.X = j;
tempBoxColorRGB = tempRGB;
imgRGBArray[i][j] = (~tempRGB).ToInt();
currentRowMaxJ = j;
}
}
else if (findCount >= 0)
{
if (RGB.FromInt(imgRGBArray[i][j]) == tempBoxColorRGB)
{
currentRowMaxJ = j;

if (!isFindRight)
{
imgRGBArray[i][j] = (~tempBoxColorRGB).ToInt();
}
else if (isFindRight && (i - startPoint.Y) < (2 * maybeHeight + 20))
{
imgRGBArray[i][j] = (~tempBoxColorRGB).ToInt();
endPoint.Y = i;
}
else if (isFindRight && (i - startPoint.Y) >= (2 * maybeHeight + 20))
{
isInBox = false;
break;
}
}
}
}

if (isFindStart)
{
findCount--;

//该行循环完成
if (rightPoint.X < currentRowMaxJ)
{
rightPoint.X = currentRowMaxJ;
rightPoint.Y = i;
rightHeight = 0;
}
else if (rightPoint.X == currentRowMaxJ)
{
rightHeight++;
}
else if (rightPoint.X > currentRowMaxJ)
{
isFindRight = true;
maybeHeight = rightPoint.Y - startPoint.Y + rightHeight / 2;
}
}
if (findCount < 0 || !isInBox)
{
break;
}
}

return new Point((startPoint.X + endPoint.X) / 2, (startPoint.Y + endPoint.Y) / 2);
}

但是这样做了以后, 发现的问题是… 跳到后面一点以后, 有颜色完全不同的方块出来了… 忘记了这一点, 如果改成全用非背景识别区域, 那么后期也会存在一个问题, 距离特别近的时候, 棋子会覆盖住这片空白空间, 用非背景色判断也会存在问题. 那么就只能换种思路了.

再从头分析一下, 跳一步以后, 下一个方块从上方落下, 随机一个点, 但是一定是最上方, 那么换一种计算方式, 先找到目标物的顶点, 而光照是从右边往左边的, 那么下一步去找目标物的右端点, 如果右端点被棋子挡住了, 说明棋子在右边, 然后去寻找左端点, 就不会出现左端点被阴影挡住了的情况.

那么接下来的分析函数就简单了, 分成几个小函数实现.

寻找顶点坐标

这里有个要注意的情况, 即棋子最高点有可能是比顶点还高的, 需要处理下这种情况, 所以需要先找到棋子位置再传入.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/// <summary>
/// 寻找顶点的方法
/// </summary>
/// <param name="imgRGBArray"></param>
/// <param name="chessPoint"></param>
/// <returns></returns>
private Point FindTopPoint(int[][] imgRGBArray, Point chessPoint)
{
RGB tempRGB;

Point topPoint = new Point(0, 0);
var hasFindTopPoint = false;

var isFirstFindIsNotBg = 0;

for (var i = 500; i < imgRGBArray.Length; i++)
{
for (var j = 0; j < imgRGBArray[i].Length; j++)
{
isFirstFindIsNotBg++;
if (!hasFindTopPoint)
{
tempRGB = RGB.FromInt(imgRGBArray[i][j]);
//if (!IsBgColor(tempRGB) && !tempRGB.TotalLike(SelfCheesColor, 30))
if (!IsBgColor(tempRGB))
{
if (isFirstFindIsNotBg == 1)
{
MessageBox.Show(string.Format("第一个点就不是BG,出错,BG颜色:{0},当前点颜色:{1}", BgColorAverage, tempRGB));
return ErrorPoint;
}

//如果当前点在自己点的空间中
if ((j > chessPoint.X - 32) && (j < chessPoint.X + 50)
&& (i > chessPoint.Y - 2) && (i < chessPoint.Y + 200))
{
continue;
}
else
{
hasFindTopPoint = true;
topPoint.Y = i;
topPoint.X = j;
break;
}
}
}
}
if (hasFindTopPoint)
{
break;
}
}
if (!hasFindTopPoint)
{
MessageBox.Show("未找到合适的顶点");
return ErrorPoint;
}
return topPoint;
}

寻找右端点坐标

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/// <summary>
/// 先寻找右端点, 如果未找到或者右端点在自己棋子区间, 返回ErrorPoint
/// </summary>
/// <param name="imgRGBArray"></param>
/// <param name="chessPoint"></param>
/// <param name="topPoint"></param>
/// <returns></returns>
private Point FindRightPoint(int[][] imgRGBArray, Point chessPoint, Point topPoint)
{
RGB tempRGB;

var currentBGColor = new RGB(imgRGBArray[topPoint.Y - 3][topPoint.X]);

//从顶点开始往右边找
//最高高度设置为200
var maxHeight = 200;
var maxWidth = 300;
Point rightPoint = new Point(0, 0);
var hasFindRightPoint = false;

for (var i = topPoint.Y; i < topPoint.Y + maxHeight; i++)
{
var thisRowIsAllBG = true;
var currentRowHasBgInMiddle = false;
for (var j = topPoint.X; j < topPoint.X + maxWidth && j < imgRGBArray[i].Length; j++)
{
if (!hasFindRightPoint)
{
tempRGB = RGB.FromInt(imgRGBArray[i][j]);

//如果当前点是背景颜色, 跳过该行
if (!thisRowIsAllBG && tempRGB.Like(currentBGColor, 10))
{
currentRowHasBgInMiddle = true;
}

if (!IsBgColor(tempRGB))
{
thisRowIsAllBG = false;
//如果当前点不是背景,而下一个点是背景
if (!currentRowHasBgInMiddle && IsBgColor(RGB.FromInt(imgRGBArray[i][j + 1])))
{
if (rightPoint.X < j)//如果已经有的rightPoint在当前点左边
{
rightPoint.Y = i;
rightPoint.X = j;
}
else if (rightPoint.X == j)
{
//如果已有的rightPoint和当前点的X坐标相同,
//那么需要接下来的几个点都不是背景,才能确定是最终点
var isFinal = true;
for (int m = 1; m < 3; m++)
{
if (!IsBgColor(RGB.FromInt(imgRGBArray[i + m][j + 1])))
{
isFinal = false;
break;
}
}
if (isFinal)
{
hasFindRightPoint = true;
break;
}
}
else if (rightPoint.X > j)
{
hasFindRightPoint = true;
break;
}
}
}

}
}
if (hasFindRightPoint)
{
break;
}
}
if (!hasFindRightPoint)
{
return ErrorPoint;
//MessageBox.Show("未找到合适的右端点, 去寻找左端点");
}
return rightPoint;
}

寻找左端点坐标

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/// <summary>
/// 如果右端点不存在或者出错的情况下, 开始寻找左端点
/// </summary>
/// <param name="imgRGBArray"></param>
/// <param name="chessPoint"></param>
/// <param name="topPoint"></param>
/// <returns></returns>
private Point FindLeftPoint(int[][] imgRGBArray, Point chessPoint, Point topPoint)
{
var currentBGColor = new RGB(imgRGBArray[topPoint.Y - 3][topPoint.X]);
var topPointColor = RGB.FromInt(imgRGBArray[topPoint.Y][topPoint.X]);

//从顶点开始往右边找
//最高高度设置为200
var maxHeight = 200;

var hasFindLeftPoint = false;
var leftPoint = new Point(0, 0);
//寻找左边端点
for (var i = topPoint.Y; i < topPoint.Y + maxHeight; i++)
{
for (var j = 100; j < topPoint.X; j++)
{
if (!hasFindLeftPoint)
{
//如果当前点不是背景色
if (!RGB.FromInt(imgRGBArray[i][j]).Like(currentBGColor, 10))
{
//如果以下8个点都不是背景,
//而且前一位的8个点都是背景
//那么就是左端点
var isFinal = true;
for (var m = 1; m <= 8; m++)
{
if (!RGB.FromInt(imgRGBArray[i + m][j - 1]).Like(currentBGColor, 10)
|| RGB.FromInt(imgRGBArray[i + m][j]).Like(currentBGColor, 10))
{
isFinal = false;
break;
}
}
if (isFinal)
{
leftPoint.Y = i;
leftPoint.X = j;
hasFindLeftPoint = true;
break;
}
}
}
}
if (hasFindLeftPoint)
{
break;
}
}
if (!hasFindLeftPoint)
{
return ErrorPoint;
}
return leftPoint;
}

寻找自己棋子坐标

这个坐标寻找方式就和之前的找目标点完全不同了, 因为棋子是不会变化的, 那么从PS中截出棋子的图片, 然后当作res把图片信息加载进来, 再整个图片查找一下对应点和像素, 就能找到自己的点了. 需要注意的是, 为了防止色差, 对自己棋子的点的颜色比较设定了一个容差5.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#region 棋子位置和颜色函数

private RGB[][] imgMySelfChessRGBColorArray;
private RGB[][] mySelfChessStart3x3Point;
private void InitMyImgARGBArray()
{
var bitmap = (Bitmap)Image.FromFile(PublicPath + @"/Res/WX-MY.png");
if (bitmap.Width != 80 || bitmap.Height != 220)
{
MessageBox.Show("资源图片错误");
return;
}
var imgMySelfRGBArray = new long[220][];

Point StartPoint = new Point(-1, -1);

imgMySelfChessRGBColorArray = new RGB[imgMySelfRGBArray.Length][];
bitmap.GetRGBA(0, 0, 80, 220, imgMySelfRGBArray);
for (var i = 0; i < imgMySelfRGBArray.Length; i++)
{
imgMySelfChessRGBColorArray[i] = new RGB[imgMySelfRGBArray[i].Length];
for (var j = 0; j < imgMySelfRGBArray[i].Length; j++)
{
var tempRGB = RGB.FromInt(imgMySelfRGBArray[i][j], true);
imgMySelfChessRGBColorArray[i][j] = tempRGB;
if (tempRGB.A == 255 && StartPoint.X == -1)
{
StartPoint.Y = i;
StartPoint.X = j;
}
}
}

mySelfChessStart3x3Point = new RGB[3][];
for (var i = 0; i < mySelfChessStart3x3Point.Length; i++)
{
mySelfChessStart3x3Point[i] = new RGB[3];
for (var j = 0; j < mySelfChessStart3x3Point[i].Length; j++)
{
mySelfChessStart3x3Point[i][j] = imgMySelfChessRGBColorArray[StartPoint.Y + i][StartPoint.X + j];
}
}
}
private Point GetMySelfChessPosition(int[][] imgRGBArray)
{
int temp_i = 0;
int temp_j = 0;

RGB tempRGB = new RGB();
var isFindStart = false;
var findCount = 50;

for (var i = 500; i < 1500; i++)
{
for (var j = 100; j < 1000; j++)
{
tempRGB = RGB.FromInt(imgRGBArray[i][j]);
if (!isFindStart)
{
if (IsMySelfChess(imgRGBArray, i, j))
{
isFindStart = true;
temp_i = i;
temp_j = j;
}
}
}
if (isFindStart)
{
findCount--;
}
if (findCount < 0)
{
break;
}
}

return new Point(temp_j, temp_i);
}

private bool IsMySelfChess(int[][] imgRGBArray, int _i, int _j)
{
for (var i = 0; i < mySelfChessStart3x3Point.Length; i++)
{
for (var j = 0; j < mySelfChessStart3x3Point[i].Length; j++)
{
if (!mySelfChessStart3x3Point[i][j].Like(RGB.FromInt(imgRGBArray[_i + i][_j + j]), 5))
{
return false;
}
}
}
return true;
}
#endregion

计算距离

最开始直接使用的两点距离计算公式, 后来发现可能需要只计算轴向距离的间隔. 但是最终测试以后, 发现直接计算距离好像也可以.

1
2
3
4
5
6
7
8

//计算轴向距离
var l = Math.Sqrt(Math.Pow(targetPoint.X - cheesCenterPoint.X, 2) + Math.Pow(targetPoint.Y - cheesCenterPoint.Y, 2));
var angle = Math.Atan2(Math.Abs(targetPoint.Y - cheesCenterPoint.Y), Math.Abs(targetPoint.X - cheesCenterPoint.X)) * 180 / Math.PI;
var space = l * Math.Cos((angle - 30) * Math.PI / 180);

//计算点距离
var space = Math.Sqrt(Math.Pow(targetPoint.X - cheesCenterPoint.X, 2) + Math.Pow(targetPoint.Y - cheesCenterPoint.Y, 2));

计算跳跃时间

这个值…纯粹靠猜和试, 毕竟分辨率不同, 测出来的像素距离也会不同…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
/// 根据距离计算按压时间
/// * 可能距离超过一定长度的时候需要减少按压时间
/// </summary>
/// <param name="space"></param>
/// <returns></returns>
public int GetPressTimeBySpace(double space)
{
double result = space * 1.4;
if (space > 600)
{
result = space * 1.35;
}
return (int)(result);
}

优化&流程

到这里以后, 基本上功能就完成了, 然后添加一些小的修补功能和自动化流程以后, 就能使用了. 不过还有个优化的地方, 就是在一次跳在正中心点以后, 下一个目标物上会出现一个白色小圈圈, 表示跳那里能出现2x加分. 利用好这个点, 可以极大的缩减点位计算幅度. 那么流程就变成如下:

  1. 寻找自己点坐标;
  2. 寻找顶点坐标(需要排除自己的位置);
  3. 试图根据顶点坐标寻找白圈;
  4. 如果找到白圈 -> 返回;
  5. 如果没找到, 寻找右端点;
  6. 如果右端点不存在或者被自己挡住, 寻找左端点;
  7. 根据端点计算出目标点距离, 返回;

试图寻找中心白圈

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/// <summary>
/// 试图寻找下一步中间的白点(如果上一步是中心点)
/// 从顶点开始, 寻找整个可能的格子空间,
/// 记录所有颜色值为白点的点.
/// 如果这些点最终能构成符合大小的圆形,
/// 那么可以确定为中心点.
/// </summary>
/// <param name="imgRGBArray"></param>
/// <param name="chessPoint"></param>
/// <param name="topPoint"></param>
/// <returns></returns>
private Point TryFindCenterWhitePoint(int[][] imgRGBArray, Point chessPoint, Point topPoint)
{
var currentBGColor = new RGB(imgRGBArray[topPoint.Y - 3][topPoint.X]);

//从顶点开始往右边找
//最高高度设置为200
var maxHeight = 200;
var maxWidth = 300;
Point centerPoint = new Point(0, 0);
var hasFindCenterPoint = false;

for (var i = topPoint.Y; i < topPoint.Y + maxHeight; i++)
{
for (var j = topPoint.X; j < topPoint.X + maxWidth; j++)
{
if (!hasFindCenterPoint)
{
if (topPoint.X - j > 0 && RGB.FromInt(imgRGBArray[i][topPoint.X - j]) == TargetCenterWhitePointColor)
{
var centerPointMay = TryFindWhiteCircleAtPosition(imgRGBArray, i, topPoint.X - j);
if (centerPointMay == ErrorPoint)
{
break;
}
else
{
centerPoint = centerPointMay;
hasFindCenterPoint = true;
}
}
if (j < imgRGBArray[i].Length && RGB.FromInt(imgRGBArray[i][j]) == TargetCenterWhitePointColor)
{
var centerPointMay = TryFindWhiteCircleAtPosition(imgRGBArray, i, j);
if (centerPointMay == ErrorPoint)
{
break;
}
else
{
centerPoint = centerPointMay;
hasFindCenterPoint = true;
}
}
}
}
if (hasFindCenterPoint)
{
break;
}
}
if (hasFindCenterPoint)
{
return centerPoint;
}
return ErrorPoint;
}

/// <summary>
/// 在指定坐标(白色圆形顶部)判断是否能找到白色圆形
/// </summary>
/// <param name="imgRGBArray"></param>
/// <param name="_i"></param>
/// <param name="_j"></param>
/// <returns></returns>
private Point TryFindWhiteCircleAtPosition(int[][] imgRGBArray, int _i, int _j)
{
var maxWhiteCircleHeight = 28;
var maxWhiteCircleWidth = 45;

var startPointSpaceBetweenCircle = maxWhiteCircleWidth / 2;

var minWhiteCircleHeight = 20;
var minWhiteCircleWidth = 35;

var leftTopPoint = new Point(1920 - 1, 1080 - 1);
var rightBottomPoint = new Point(0, 0);

var centerPoint = new Point(0, 0);
var centerPointAddCount = 0;

//根据平均统计,找到最中心的点
for (var i = _i; i < _i + maxWhiteCircleHeight; i++)
{
for (var j = _j - startPointSpaceBetweenCircle; j < _j + maxWhiteCircleWidth + startPointSpaceBetweenCircle; j++)
{
if (j < 0 || j > imgRGBArray[0].Length)
{
continue;
}
if (RGB.FromInt(imgRGBArray[i][j]) == TargetCenterWhitePointColor)
{
leftTopPoint.Y = Math.Min(leftTopPoint.Y, i);
leftTopPoint.X = Math.Min(leftTopPoint.X, j);
rightBottomPoint.Y = Math.Max(rightBottomPoint.Y, i);
rightBottomPoint.X = Math.Max(rightBottomPoint.X, j);
centerPoint.Y += i;
centerPoint.X += j;
centerPointAddCount++;
}
}
}
centerPoint = new Point(centerPoint.X / centerPointAddCount, centerPoint.Y / centerPointAddCount);

var middlePoint = new Point((leftTopPoint.X + rightBottomPoint.X) / 2, (leftTopPoint.Y + rightBottomPoint.Y) / 2);

//判断该点周围全是白色
for (var i = centerPoint.Y - 3; i <= centerPoint.Y + 3; i++)
{
for (var j = centerPoint.X - 10; j <= centerPoint.X + 10; j++)
{
if (RGB.FromInt(imgRGBArray[i][j]) != TargetCenterWhitePointColor)
{
return ErrorPoint;
}
}
}
//最大距离之外不是白色, 最小距离之内全是白色
if (RGB.FromInt(imgRGBArray[centerPoint.Y - maxWhiteCircleHeight / 2][centerPoint.X]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[centerPoint.Y + maxWhiteCircleHeight / 2][centerPoint.X]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[centerPoint.Y][centerPoint.X - maxWhiteCircleWidth / 2]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[centerPoint.Y][centerPoint.X + maxWhiteCircleWidth / 2]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[centerPoint.Y - minWhiteCircleHeight / 2][centerPoint.X]) != TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[centerPoint.Y + minWhiteCircleHeight / 2][centerPoint.X]) != TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[centerPoint.Y][centerPoint.X - minWhiteCircleWidth / 2]) != TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[centerPoint.Y][centerPoint.X + minWhiteCircleWidth / 2]) != TargetCenterWhitePointColor)
{
return ErrorPoint;
}

//最大边框处不应该有白色
if (RGB.FromInt(imgRGBArray[leftTopPoint.Y][leftTopPoint.X]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[leftTopPoint.Y][rightBottomPoint.X]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[rightBottomPoint.Y][rightBottomPoint.X]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}
if (RGB.FromInt(imgRGBArray[rightBottomPoint.Y][leftTopPoint.X]) == TargetCenterWhitePointColor)
{
return ErrorPoint;
}

SetPointWithRectColor(imgRGBArray, leftTopPoint.Y, leftTopPoint.X, 3, CenterBoxColor);
SetPointWithRectColor(imgRGBArray, leftTopPoint.Y, rightBottomPoint.X, 3, CenterBoxColor);
SetPointWithRectColor(imgRGBArray, rightBottomPoint.Y, rightBottomPoint.X, 3, CenterBoxColor);
SetPointWithRectColor(imgRGBArray, rightBottomPoint.Y, leftTopPoint.X, 3, CenterBoxColor);

return centerPoint;
}

自动化

开启一个新线程执行循环跳, 再点一次干掉线程.

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
private void BtnStartJump_Click(object sender, EventArgs e)
{
isDoingJump = !isDoingJump;
if (isDoingJump)
{
myLog.AppendToFile(LogFilePath, "开始跳...");
BtnStartJump.Text = "正在跳...";
jumpThread = new Thread(() =>
{
while (isDoingJump)
{
GetAndroidScreen();
var space = AnalyseSpace();
System.Threading.Thread.Sleep(JumpDelay);
var pressTime = GetPressTimeBySpace(space);
ExecCmd(ADBPath + string.Format("shell input swipe 500 500 501 501 {0}", pressTime));
System.Threading.Thread.Sleep(pressTime + ShotScreenDelay);
}
});
jumpThread.Start();
}
else
{
myLog.AppendToFile(LogFilePath, "跳完了");
BtnStartJump.Text = "开始跳!";
jumpThread.Abort();
jumpThread = null;
}
}

日志记录

然后就是加入日志模块, 和图片保存, 将每次跳和计算的结果都保存起来, 以供之后分析.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public class JumpLog
{
public JumpLog()
{
}

public JumpLog(DateTime shotScreenTime)
{
this.ShotScreenTime = shotScreenTime;
}

public DateTime ShotScreenTime { get; set; }
public DateTime AnalyseStartTime { get; set; }
public DateTime AnalyseEndTime { get; set; }
public int AnalyseTime
{
get
{
return (int)(AnalyseEndTime - AnalyseStartTime).TotalMilliseconds;
}
}
public double AnalyseSpace { get; set; }
public Point TopPointPosition { get; set; }
public Point RightPointPosition { get; set; }
public Point LeftPointPosition { get; set; }
public Point CenterWhitePointPosition { get; set; }
public JumpLogPointPositionType GetPointPositionType { get; set; }
public Point TargetPointPosition { get; set; }
public Point SelfChessPointPosition { get; set; }
public int PressTime { get; set; }
public string ScreenImageFileName { get; set; }
public void AppendToFile(string filePath, string content)
{
using (var fileStream = File.Open(filePath, FileMode.Append, FileAccess.Write))
{
var writeStream = new StreamWriter(fileStream, Encoding.UTF8);
writeStream.WriteLine(string.Format("[{0}] : {1}", DateTime.Now.GetCommonDateString(), content));
writeStream.Flush();
writeStream.Close();
}
}
public void AppendToFile(string filePath)
{
AppendToFile(filePath, GetLogString());
}
private string GetLogString()
{
return string.Format("[截图时间:{0}] [分析耗时:{1}ms] [分析最终距离:{2}px] [按压时间:{3}] [定位模式:{4}] [顶点坐标:{5}] [定位点坐标:{6}] [目标点坐标:{7}] [棋子坐标:{8}] [图片名称:{9}]",
ShotScreenTime.GetCommonDateString(), AnalyseTime, AnalyseSpace.ToString("0.00"), PressTime,
GetPointPositionType.GetDescription(), TopPointPosition.ToString(),
GetRightAnchorPointPosition().ToString(), TargetPointPosition.ToString(),
SelfChessPointPosition.ToString(), ScreenImageFileName);
}

private Point GetRightAnchorPointPosition()
{
switch (GetPointPositionType)
{
case JumpLogPointPositionType.Center: return CenterWhitePointPosition;
case JumpLogPointPositionType.Right: return RightPointPosition;
case JumpLogPointPositionType.Left: return LeftPointPosition;
}
return new Point(-1, -1);
}
}
public enum JumpLogPointPositionType
{
[Description("通过中心白点定位")]
Center = 1,
[Description("通过右端点定位")]
Right = 2,
[Description("通过左端点定位")]
Left = 4,
}

public static class StaticJumpLogPointPositionTypeHelp
{
public static string GetDescription(this JumpLogPointPositionType type)
{
FieldInfo EnumInfo = type.GetType().GetField(type.ToString());
DescriptionAttribute[] EnumAttributes = (DescriptionAttribute[])EnumInfo.
GetCustomAttributes(typeof(DescriptionAttribute), false);
if (EnumAttributes.Length > 0)
{
return EnumAttributes[0].Description;
}
return type.ToString();
}
}

运行结果





作者

Mosby

发布于

2018-01-06

更新于

2018-01-11

许可协议

评论