手把手教你用人工智慧玩跳一跳
手把手教你用人工智慧玩跳一跳
請開始你的表演!
01
怎麼通過電腦控制手機?
首先介紹一下,這個程序所用到的一個很關鍵的工具—adb工具包。這個工具由谷歌公司提供,它的全稱為Android Debug Bridg,藉助這個工具,可以通過命令的方式在電腦上對手機進行操作。
下面說幾條寫程序要用到的命令:
1.截取屏幕保存為screen.png並將圖片放在手機/sdcard目錄下:
adb shell screencap -p /sdcard/screen.png
2.將/sdcard目錄下的screen.png上傳到電腦(程序運行的當前目錄):
adb pull /sdcard/screen.png
3.在(200,500)處按下手機屏幕,並在(200,500)處抬起,時長1秒:
adb shell input swipe 200 500 200 500 1000
02
第一版—機械+半自動
我們知道,跳一跳這個遊戲操作非常簡單,只需要按屏幕,根據按屏幕的時間長短來決定跳出去的位置。我們現在有了adb工具,那麼只需要根據人與盒子之間的距離長短向手機發送一條按屏幕的指令,但是到底按多久?
按屏幕的時間長短和距離是成正比的,雖然我們不知道按屏幕的時間長短具體是多少,但是我們可以獲取到人與盒子之間的距離,得到這個距離乘上一個固定的值(這個值自己測試一下就能得到)就是按屏幕的時間。
所以剛開始我用直尺去量小人到盒子中間的距離,然後輸入距離,程序計算出按屏幕的時間,再向手機發送指令,人就會老老實實地跳到盒子上去。
代碼示例:
#include
#include
#define RATE 4.1 //這個值自己測試得到
int main()
{
double distance;
double time;
char str[100];
while (true) {
printf("請輸入距離:");
scanf("%f", &distance);
time = distance * RATE; //根據比率得到時間
sprintf(str, "adb shell input swipe 200 500 200 500 %d", (int)time); //生成命令字元串
system(str); //執行命令
}
return 0;
}
通過這種方式,只要用尺子量精確了,那麼跳的也是相當精確的,但是缺點就是非常累人,要手動測量,再把測量的值輸入到程序,效率比較低。
03
第二版—手工+半自動
由於第一版效率太低,還要用直尺去測量,為了偷懶所以想到了一個更好的辦法。我們可以通過獲取截圖的方式把跳一跳的遊戲界面通過圖片形式上傳到電腦,然後通過QT顯示出這張圖的圖像並對這張圖進行操作。
原本我們用直尺去量人和盒子的距離,那麼現在,只需要通過滑鼠點擊一下人,記錄下人的坐標,然後點擊一下盒子,記錄下盒子的坐標,通過這兩個點的坐標計算出這兩個點的距離,這樣通過兩次點擊就得到了人與盒子的距離,也不用手動測量和手動輸入顯然是省了很多力氣。
示例代碼(部分):
/*
//滑鼠點擊的點結構體
struct PointClick{
float x;
float y;
};
*/
//獲取滑鼠點擊事件
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
char str[100];
//如果第一個點還未點擊
if(!FirstIsClick){
point_click.x=x;
point_click.y=y;
FirstIsClick=true;
}
else{
//計算兩個點的距離
double distance=sqrt((double)((x-point_click.x)*(x-point_click.x)+(y-point_click.y)*(y-point_click.y)));
//得出時間
double time=distance*4.1;
//生成命令字元串
sprintf(str,"adb shell input swipe 200 500 200 500 %d",(int)time);
system(str); //執行命令
Sleep(800);
init();
FirstIsClick=false;
}
}
//初始化函數
void Widget::init()
{
system("adb shell screencap -p /sdcard/screen.png"); //獲取截圖到手機
system("adb pull /sdcard/screen.png"); //將截圖上傳到電腦
//載入圖片
pixmap.load( "screen.png" );
//測試手機像素為1280*720,所以顯示的時候縮小兩倍顯示
pixmap = pixmap.scaled(w_width/2, w_heigh/2,Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
palette.setBrush(QPalette::Window, QBrush(pixmap));
this->setPalette(palette);
}
第二版相較於第一版確實是省了不少力氣,但是,畢竟還是需要手動點擊,需要人一直坐在電腦前,離了人不行。那麼就有了第三版——全自動跳一跳。
03
人工智慧版—自動跳
首先面臨一個最重要的問題如何讓程序完全脫離人工實現自動呢?那麼需要讓程序具備分析的能力。我們從「像素」這一個角度切入。
不難發現跳一跳遊戲的背景色和盒子或者小人的顏色差距是很大的,那麼我們選一個點只可能是背景的點,獲取這個像素的RGB並記錄下來,然後對這張圖的中間部分,只可能出現盒子和人的區域進行「地毯式」掃描,從上到下一行一行地逐個像素地獲取顏色與背景色進行比較,如果顏色相似,那就證明是背景,那就繼續向下掃描,直到出現一個與背景色差別很大的像素,那麼這個點一定是將要跳的盒子的最頂點。
根據這頂點的坐標,向右130像素然後沿Y軸由上向下掃描70個像素,如果某一個像素和背景色差別很大,那麼這個點就是盒子的最右角,根據最盒子頂點的Y軸坐標和最右點的X軸坐標我們就可以確定盒子的中心坐標。
得到了盒子的坐標,那麼人的坐標怎麼獲得呢?我們可以看到人的顏色是固定的,那麼我們就在中間區域自下向上一行一行地掃描,掃到和人的顏色相同的像素那麼這個點就是人的底部的坐標。
根據這兩個坐標就可以計算出距離,從而也就可以得到按屏幕的時間了。循環此步驟,截圖然後分析,然後執行分析後的數據。這就使得跳一跳外掛可以自動運行了。
示例代碼:
autojump.h
#ifndef AUTOJUMP_H
#define AUTOJUMP_H
#include
#include
class AutoJump
{
public:
AutoJump();
void init();
void work();
bool colorRight(QColor color1,QColor color2,double diff);
QImage bkImg;
QColor stopColor;
QColor bkColor;
QColor nowbkColor;
QColor peopleColor;
int boxX;
int boxY;
int peopleX;
int peopleY;
int lastPeopleX;
int lastPeopleY;
float distance;
float time;
char str[100];
};
#endif // AUTOJUMP_H
autojump.cpp
#include "autojump.h"
#include
#include
AutoJump::AutoJump()
{
peopleColor.setRgb(55,60,102);
stopColor.setRgb(42,42,50);
lastPeopleX = 0;
lastPeopleY = 0;
}
void AutoJump::init()
{
while(true){
work();
}
}
void AutoJump::work()
{
qDebug()
//獲取截圖
system("adb shell screencap -p /sdcard/screen.png");
system("adb pull /sdcard/screen.png");
//載入
bkImg.load("screen.png");
qDebug()
//獲取背景色
bkColor = bkImg.pixel(300,180);
//判斷是不是遊戲結束,結束自動重啟遊戲
if(colorRight(stopColor,bkColor,1))
{
qDebug()
system("adb shell input tap 355 1060");
qDebug()
Sleep(3000);
qDebug()
system("adb shell screencap -p /sdcard/screen.png");
system("adb pull /sdcard/screen.png");
bkImg.load("screen.png");
bkColor = bkImg.pixel(300,180);
}
boxX = 0;
boxY = 0;
//獲取盒子頂部的位置,確定x
int topY = 0;
for(int y = 200; y
{
for(int x = 80; x
{
nowbkColor = bkImg.pixel(x,y);
if(!colorRight(bkColor,nowbkColor,6))
{
if(colorRight(peopleColor,nowbkColor,2)){
continue;
}else{
boxX = x;
topY = y;
break;
}
}
}
if(boxX != 0)
{
break;
}
}
qDebug()
//獲取盒子右角的位置,確定y
int rightX = 0;
for(int x = boxX+130;x>boxX;x-=2 )
{
for(int y = topY;y
{
nowbkColor = bkImg.pixel(x,y);
if(!colorRight(bkColor,nowbkColor,6))
{
if(colorRight(peopleColor,nowbkColor,20)){
continue;
}else{
rightX = x;
boxY = y;
break;
}
}
}
if(boxY != 0)
{
break;
}
}
qDebug()
//獲取人的位置
peopleX = 0;
peopleY = 0;
for(int y = 750; y > 500; y-=2)
{
for(int x = 80; x
{
nowbkColor = bkImg.pixel(x,y);
if(colorRight(peopleColor,nowbkColor,0))
{
peopleX = x;
peopleY = y;
break;
}
}
if(peopleX != 0)
{
break;
}
}
peopleX += 5;
qDebug()
//計算按屏幕時間
distance=sqrt((boxX-peopleX)*(boxX-peopleX)+(boxY-peopleY)*(boxY-peopleY));
time=distance*2.03;
sprintf(str,"adb shell input swipe 200 500 200 500 %d",(int)time);
system(str);
Sleep(800);
//system("pause");
}
bool AutoJump::colorRight(QColor color1,QColor color2,double diff)
{
//判斷一下這兩個顏色rgb值在三維坐標中的位置差值
if(sqrt(double((color1.red()-color2.red())+(color1.green()-color2.green())+(color1.blue()-color2.blue())))
return true;
return false;
}
總結
第一版是用C語言寫的,第二版第三版才是真正需要用到QT的,但是QT僅僅是一種方式,其他還有很多途徑也有很多方法可以實現。這裡只分享下思路。
這三版跳一跳由簡到難,但是人工干預也由多變無,對於思路的梳理還是相當有幫助的。
TAG:編程大會 |