跳一跳自動跳躍C#代碼實現
最近這款“跳一跳”很火,在段子里面看到有人才放了張畫著坐標的紙在手機上,說根據距離確定摁的“嘟”的次數,還有通過程序來實現自動計算的。看得心血來潮忍不住來試一試?話不多說,先上圖。
因為比較急著做出成品,所以細節(jié)上沒多細摳。感覺設置的跳躍速度稍快了一點,有興趣的同學可以實測一下。也有一個因素是測試時后臺程序比較多,影響了結果。
原理其實也是跟大家想的一樣很簡單,無非就是三個要素:距離、速度、時間。就是通過當前小藍人腳底所在的像素坐標和目標平臺中心像素的坐標計算距離,除以事先通過測試得出的速度,得出觸摸屏幕時間,由程序發(fā)出“觸摸”指令,實現定點跳躍。不過在做自動計算跳躍所需觸摸時間之前還是要做一些準備功夫的。下面直接說一下詳細的過程吧。
準備工作:
1、通過PS等工具獲取①小藍人最底下一行(作為當前位置Y坐標)和最左邊一列(作為當前位置X坐標)的像素RGB,實測在本機基本都是一樣的X(54,63, 102),Y(43, 43, 73)。圖片左上角、右下角坐標分別為[0,0][Xmax,Ymax]。②獲取小藍人的頭的寬度(所占像素點)。③獲取左上角分數最底下一行的像素y坐標。
2、通過指令
adb shell input touchscreen swipe x y x y 延時(ms)
(x、y為觸摸屏幕的坐標),結合photoshop測試出“跳一跳”每一條的速度。本例中測得結果約為17 / 24(pixel/ms),實際游戲中的速度略小于這個速度。大家用代碼可以精確測一下,我已經沒耐心了0.0。
3、電腦準備好調試環(huán)境(因為窮所以測試用的是自己的Android機,所以要準備好ADK(platform-tools/adb.exe);另外本次測試語言是C#)
4、手機開啟調試模式,連接電腦,打開“跳一跳”
過程:
一、獲取設備號(獲取序列號,或者直接查看手機信息),指令:
adb devices
二、截取手機當前畫面到sd卡(本機存儲格式為png,實測手機按鍵截屏為jpg(失真)),指令:
adb -s 設備號 shell screencap -p /sdcard/temp.png
三、復制文件到電腦,指令:
adb -s 設備號 pull /sdcard/temp.png 保存路徑
四、刪除文件,指令:
adb -s 設備號 shell rm /sdcard/temp.png
五、獲取小藍人腳底像素坐標和目標平臺中心像素坐標,下面詳細說說里面的步驟
1、通過Bitmap類讀取圖片,再用unsafe代碼利用指針把RGB數據直接從內存拷出來存放到byte數組中(這步其實不用也可以但不知道直接通過Bitmap獲取像素效率會不會很低,大家可以測了分享一下結果)
2、用兩層循環(huán)y從max->0,遍歷x軸像素,通過對比找出小藍人位置,本例通過兩個rgb像素的標準差不超過3作為置信偏差判斷兩個像素是否為同一元素。再稍微處理一下就可得出當前坐標。
3、利用上面得到的坐標P以及一開始準備工作中提到的分數底行y坐標(取大于該y作為startY即可)再進行對目標坐標的搜索:用兩層循環(huán)y從startY->Py,遍歷x軸像素(利用P的x坐標縮小搜索的x坐標范圍:若x位于左半屏則搜索Px+40->Xmax,反之搜索0->Px-40,注:不縮小范圍會出錯,原因大家想想)。(這個40可取大于小藍人頭寬度一半的值即可)
4、那就用我們的勾三股四弦五定理再開根求出距離。距離除以速度得出時間。
六、發(fā)送觸摸指令實現定時跳躍,指令:
adb shell input touchscreen swipe x y x y延時(ms)
這里不得不說一下,當時找半天找不到定時觸摸的指令,網上有個用6個指令組合實現定時觸摸屏幕的方法,但實測無效,而且也怕指令這么多,延時還是分開控制,肯定會對跳躍結果有很大影響。后面看到一條利用swipe指令實現的評論,真是醒目。swipe雖然是滑動指令,但如果設置起止坐標都是同一個坐標不就相當于實現了定點定時觸摸了嗎。
七、七就是一直重復二~六的步驟就是了。
本次測試很東西都是急著做,沒仔細研究,例如獲取跳躍速度這個就是傻瓜式的通過手動發(fā)送跳躍指令、截圖用ps手動計算出來的。大家可以用代碼實現一下。希望大家指正可以改進的地方。
C#源碼如下
Cmd類,實現cmd執(zhí)行命令
class Cmd
{
private System.Diagnostics.Process process;
private bool isExecuted; // 是否執(zhí)行過命令
private string command; // 上次執(zhí)行命令
private int result; // 上次執(zhí)行命令結果
private string resultContent; // 上次執(zhí)行命令返回結果
public Cmd()
{
process = new System.Diagnostics.Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.UseShellExecute = false; //是否使用操作系統shell啟動
process.StartInfo.RedirectStandardInput = true;//接受來自調用程序的輸入信息
process.StartInfo.RedirectStandardOutput = true;//由調用程序獲取輸出信息
process.StartInfo.RedirectStandardError = true;//重定向標準錯誤輸出
process.StartInfo.CreateNoWindow = true;//不顯示程序窗口
isExecuted = false;
}
public int ExecuteCmd(string cmd)
{
command = cmd;
try
{
process.Start();
process.StandardInput.WriteLine(cmd + "&exit");
process.StandardInput.AutoFlush = true;
string content = process.StandardOutput.ReadToEnd();
process.WaitForExit();//等待程序執(zhí)行完退出進程
process.Close();
result = 0;
resultContent = content.Split(new string[] { "&exit" }, StringSplitOptions.None)[1].Replace("\n", "");
}
catch (Exception ex)
{
result = -1;
resultContent = ex.Message;
}
if (!isExecuted) isExecuted = true;
return result;
}
private int ExecuteCmd(string adbPath, string cmd)
{
command = $"\"{adbPath}\" {cmd}";
try
{
process.Start();
process.StandardInput.WriteLine(command + "&exit");
process.StandardInput.AutoFlush = true;
string content = process.StandardOutput.ReadToEnd();
process.WaitForExit();//等待程序執(zhí)行完退出進程
process.Close();
result = 0;
resultContent = content.Split(new string[] { "&exit" }, StringSplitOptions.None)[1].Replace("\n", "");
}
catch (Exception ex)
{
result = -1;
resultContent = ex.Message;
}
if (!isExecuted) isExecuted = true;
return result;
}
public string GetExcResult()
{
if (isExecuted)
{
if (result == 0)
{
return resultContent;
}
else
{
return $"Execute Failed! Command:{command}\n{resultContent}";
}
}
else
{
return "從未執(zhí)行過命令";
}
}
public void DisposeProcess()
{
process.Dispose();
}
}
class Pixel
{
public byte[] pixel = new byte[3];
public Pixel()
{
}
}
Pixel類,存放RGB字節(jié)
class Pixel
{
public byte[] pixel = new byte[3];
public Pixel()
{
}
}
PlayJumpJump類,實現主要分析計算和跳躍操作
class PlayJumpJump
{
private static readonly int confidenceItv = 3; // 兩個rgb標準差小于等于3認為是同一元素
private static readonly Pixel manXRgb = new Pixel { pixel = new byte[] { 54, 63, 102 } }; // 小人X坐標rgb
private static readonly Pixel manYRgb = new Pixel { pixel = new byte[] { 43, 43, 73 } }; // 小人Y坐標rgb
private static readonly double startYPer = 0.15625; // 分數下一行Y為第289,取 300 / 1920 = 0.15625, 從下一行開始搜索目標
private static readonly double Speed = 17.0 / 24; // 速度,最重要的因素,這也是約摸算出來的
private static readonly string[] TouchCoor = new string[] { "800", "1700" }; // 觸屏位置
private static readonly string Format = "png"; // 本人用機子截取為png,也可不設格式(實測bitmap與ps cc打開同一jpg,同一像素點rgb值不一致,懷疑是bitmap打開jpg會有失真)
private static readonly string TempDir = "/sdcard/";
private static readonly string SaveDir = "temp/";
private static readonly string CaptureScreen_Command = $"-s {{0}} shell screencap -p {TempDir}{{1}}";
private static readonly string CopyFile_Command = $"-s {{0}} pull {TempDir}{{1}} \"{SaveDir}{{1}}\"";
private static readonly string RemoveFile_Command = $"-s {{0}} shell rm {TempDir}{{1}}";
private static readonly string LongPress_Command = "shell input touchscreen swipe {0} {1} {0} {1} {2}";
private Cmd myCmd;
private string adbCmdPrefix;
private string result;
public List<string> devices;
public PlayJumpJump(string adbPath)
{
myCmd = new Cmd();
adbCmdPrefix = $"\"{adbPath}\" ";
if (!Directory.Exists(SaveDir))
{
Directory.CreateDirectory(SaveDir);
}
}
public void Init()
{
myCmd = new Cmd();
}
public bool GetDevices()
{
devices = new List<string>();
myCmd.ExecuteCmd(ReturnCommand("devices"));
result = myCmd.GetExcResult();
foreach (string line in result.Split(new char[] { '\n'}))
{
if (line.Contains("device"))
{
List<string> items = line.Split(new char[] { '\t', '\r' }, StringSplitOptions.None).ToList();
if (items.Count > 1)
{
devices.Add(items[items.IndexOf("device") - 1]);
}
}
}
return devices.Count > 0 ? true : false;
}
public string CaptureScreen()
{
string fileName = $"temp{DateTime.Now.ToString("HHmmssfff")}.{Format}";
myCmd.ExecuteCmd(ReturnCommand(CaptureScreen_Command, new string[] { devices[0], fileName }));
myCmd.ExecuteCmd(ReturnCommand(CopyFile_Command, new string[] { devices[0], fileName }));
myCmd.ExecuteCmd(ReturnCommand(RemoveFile_Command, new string[] { devices[0], fileName }));
return AppDomain.CurrentDomain.BaseDirectory + SaveDir + fileName;
}
public static unsafe Pixel[][] GetPixelArray(string path)
{
Bitmap bitmap = new Bitmap(path);
int depth = Image.GetPixelFormatSize(bitmap.PixelFormat);
if (depth == 24)
{
int width = bitmap.Width;
int height = bitmap.Height;
Pixel[][] pixelArray = new Pixel[height][];
for (int i = 0; i < pixelArray.Length; i++) pixelArray[i] = new Pixel[width];
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
byte* ptr = (byte*)bmpData.Scan0;
for (int i = 0; i < pixelArray.Length; i++)
{
for (int j = 0; j < pixelArray[i].Length; j++)
{
pixelArray[i][j] = new Pixel { pixel = new byte[] { *(ptr + 2), *(ptr + 1), *ptr } };
ptr += 3;
}
ptr += bmpData.Stride - 3 * bmpData.Width; // 減去占位字節(jié)(可能出于性能或兼容性考慮,Stride為4的倍數)
}
bitmap.UnlockBits(bmpData);
return pixelArray;
}
else if (depth == 32)
{
int width = bitmap.Width;
int height = bitmap.Height;
Pixel[][] pixelArray = new Pixel[height][];
for (int i = 0; i < pixelArray.Length; i++) pixelArray[i] = new Pixel[width];
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
byte* ptr = (byte*)bmpData.Scan0;
for (int i = 0; i < pixelArray.Length; i++)
{
for (int j = 0; j < pixelArray[i].Length; j++)
{
pixelArray[i][j] = new Pixel { pixel = new byte[] { *(ptr + 2), *(ptr + 1), *ptr } };
ptr += 4; // 每3個字節(jié)忽略1個透明度字節(jié)
}
}
bitmap.UnlockBits(bmpData);
return pixelArray;
}
else
{
return null;
}
}
public void Jump2Happy()
{
string picture = CaptureScreen();
Pixel[][] pixelArray = GetPixelArray(picture);
int[] curCoor = GetCurCoordinates(pixelArray);
int[] destCoor = GetDestCoordinates(pixelArray, curCoor);
double distance = Math.Round(Math.Sqrt(Math.Pow(Math.Abs(destCoor[0] - curCoor[0]), 2) + Math.Pow(Math.Abs(destCoor[1] - curCoor[1]), 2)), 3);
int time = (int)(distance / Speed);
Console.WriteLine($"from [{curCoor[0]},{curCoor[1]}]\tto [{destCoor[0]},{destCoor[1]}] distance≈{distance} take≈{time}ms ==>> Jump ");
myCmd.ExecuteCmd(ReturnCommand(LongPress_Command, new string[] { TouchCoor[0], TouchCoor[1], time.ToString() }));
}
public static int[] GetCurCoordinates(Pixel[][] pixelArray)
{
int[] coordinates = new int[2];
List<int[]> xList = new List<int[]>();
List<int[]> yList = new List<int[]>();
// y從max -> 0,遍歷x軸像素
for (int i = pixelArray.Length - 1; i >= 0; i--)
{
for (int j = 0; j < pixelArray[i].Length; j++)
{
if (isSameElement(pixelArray[i][j], manXRgb, confidenceItv))
{
xList.Add(new int[] { j, i });
}
}
if (xList.Count > 0) break;
}
coordinates[0] = xList.Count > 0 ? (xList[0][0] + xList[xList.Count - 1][0]) / 2 : 0;
// x從0 -> max,遍歷y軸像素
for (int i = 0; i < pixelArray[0].Length; i++)
{
for (int j = pixelArray.Length - 1; j >= 0; j--)
{
if (isSameElement(pixelArray[j][i], manYRgb, confidenceItv))
{
yList.Add(new int[] { i, j });
}
}
if (yList.Count > 0) break;
}
coordinates[1] = yList.Count > 0 ? (yList[0][1] + yList[yList.Count - 1][1]) / 2 : 0;
return coordinates;
}
public static int[] GetDestCoordinates(Pixel[][] pixelArray, int[] curCoor)
{
Pixel enviRgb; // 排除rgb采樣
Pixel destRgb = null; // 采樣
int[] coordinates = new int[2];
List<int[]> xList = new List<int[]>();
List<int[]> yList = new List<int[]>();
int startY = (int)(pixelArray.Length * startYPer);
int start, end, inc;
if (curCoor[0] < (pixelArray[0].Length / 2))
{
start = curCoor[0] + 40;
end = pixelArray[0].Length;
}
else
{
start = 0;
end = curCoor[0] - 40;
}
// y從0 -> max,遍歷x軸像素
for (int i = startY; i < pixelArray.Length; i++)
{
enviRgb = pixelArray[i][0];
for (int j = start; j < end; j++)
{
if (!isSameElement(pixelArray[i][j], enviRgb, confidenceItv))
{
xList.Add(new int[] { j, i });
if (destRgb == null) destRgb = pixelArray[i][j];
}
}
if (xList.Count > 0) break;
}
coordinates[0] = xList.Count > 0 ? (xList[0][0] + xList[xList.Count - 1][0]) / 2 : 0;
// x從0 -> max,遍歷y軸像素
if (coordinates[0] < (pixelArray[0].Length / 2))
{
start = 0;
end = pixelArray[0].Length - 1;
inc = 1;
}
else
{
start = pixelArray[0].Length - 1;
end = 0;
inc = -1;
}
bool isFond = false;
for (int i = start; i != end; i+=inc)
{
for (int j = startY; j < curCoor[1]; j++)
{
if (isSameElement(pixelArray[j][i], destRgb, confidenceItv))
{
coordinates[1] = j;
isFond = true;
break;
}
}
if (isFond) break;
}
return coordinates;
}
public static bool isSameElement(Pixel pixel1, Pixel pixel2, int confidence)
{
return Math.Pow(pixel1.pixel[0] - pixel2.pixel[0], 2) + Math.Pow(pixel1.pixel[1] - pixel2.pixel[1], 2) + Math.Pow(pixel1.pixel[2] - pixel2.pixel[2], 2) <= 3 * Math.Pow(confidence, 2);
}
public string ReturnCommand(string command, string[] parameter)
{
return adbCmdPrefix + string.Format(command, parameter);
}
public string ReturnCommand(string command, string parameter)
{
return adbCmdPrefix + string.Format(command, parameter);
}
public string ReturnCommand(string command)
{
return adbCmdPrefix + command;
}
public void DisposeProcess()
{
myCmd.DisposeProcess();
myCmd = null;
}
測試:
static void Main(string[] args)
{
string adbPath = ""; // adb.exe路徑
PlayJumpJump testPlay = new PlayJumpJump(adbPath);
if (testPlay.GetDevices())
{
while (true)
{
testPlay.Jump2Happy();
Thread.Sleep(1200);
}
}
testPlay.DisposeProcess();
Console.ReadKey();
}
}
更多內容大家可以參考專題《微信跳一跳》進行學習。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持我們。
上一篇:大家應該掌握的多線程編程
欄 目:C#教程
下一篇:C# 操作Excel代碼總結
本文標題:跳一跳自動跳躍C#代碼實現
本文地址:http://www.jygsgssxh.com/a1/C_jiaocheng/5296.html
您可能感興趣的文章
- 01-10WinForm實現程序一段時間不運行自動關閉的方法
- 01-10C#.NET實現網頁自動登錄的方法
- 01-10C#實現的三種模擬自動登錄和提交POST信息的方法
- 01-10C#網頁跳轉方法總結
- 01-10舉例講解C#中自動實現的屬性
- 01-10C#設置軟件開機自動運行的方法(修改注冊表)
- 01-10WPF自動隱藏的消息框的實例代碼
- 01-10WinForm天貓雙11自動搶紅包源碼分享
- 01-10C# winform 模擬鍵盤輸入自動接入訪問網絡的實例
- 01-10C# 實現ADSL自動斷網和撥號的方法(適用于撥號用戶)


閱讀排行
本欄相關
- 01-10C#通過反射獲取當前工程中所有窗體并
- 01-10關于ASP網頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現txt定位指定行完整實例
- 01-10WinForm實現仿視頻播放器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現讀取注冊表監(jiān)控當前操作系統已
隨機閱讀
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 01-10delphi制作wav文件的方法
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10C#中split用法實例總結
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-11ajax實現頁面的局部加載
- 08-05dedecms(織夢)副欄目數量限制代碼修改


