跳一跳辅助工具的原理分析,和Java实现。(其实没那么复杂)

一、前言

(Java代码的实现是基于另外一篇博客,我精简了计算方法而成,参考博客地址http://blog.csdn.net/lihushiwoa/article/details/78942322)

先说一说我的感受,之前觉得能做出做出辅助工具的技术要求一定很高,然而当自己真正分析原理并且动手实现之后发现门槛没有那么高。

想做这个工具首先得知道adb是什么,其次是会一门编程语言。我了解adb之后瞬间觉得思路豁然开朗。

adb是Android Debug Bridge。他的作用是可以通过在计算机cmd输入命令控制Android手机,比如马上要用到的效果----模拟实现触控屏幕和触控多长时间。所以你在java代码里控制adb命令是可行的。

二、思路与原理

想象一下手动能让棋子命中下一个platform的场景:

1:测量棋子到目标坐标的距离S;

2:按压时间T=S*时间系数;(时间系数:单位位移的用时,200ms的n倍,多次尝试的大致确认

  然后你按屏幕时间T就可以了。

在本代码中我发现每个跳板的连线与竖直方向大约呈60°夹角(跳板和跳板虽然是60°,但是棋子不一定与目标是60°角关系呀?没关系,经过测试与棋子的角度没太大关系),所以如果我们知道棋子和目标点的距离就可以算S了。

测量夹角计算斜边原理

代码实现也一样:

1:adb命令截屏,存放到指定文件夹。adb命令分别是:

adb shell screencap -p /sdcard/current.png

adb pull /sdcard/current.png  d:/jump_screencapture 

2:扫描棋子的X轴坐标。

图片是一个个像素点组成,从上往下遍历每一行像素点,如果某像素点的R、G、B值在棋子的RGB值区间(R∈(50,60),G∈(53,63),B∈(95,110)),那么可以确定该像素点在棋子内了,然后记下这是第几个(halmaXCount),并且计算横坐标的和halmaSum+=halmaSum(原理:多次测量取平均值,精确棋子的X轴坐标)。

3:扫描目标跳板的横坐标。原理与扫描棋子相似,但又不同。

原理:由于跳板是左右对称的,也就是说假如目标跳板的上表面是标准的菱形,那么一旦遍历到菱形上方的顶点,那么该顶点的X坐标就是目标跳板上表面中心的X坐标。

不同之处是:遍历完目标调班的首行像素点便停止,(缺点:样本空间略小,可能导致目标坐标不精确,留日后优化。)。记录每行首个像素点的RGB值之后,在接下来的遍历中如果发现本行与首个像素点RGB差异太大,则认为该像素点已经在目标跳板之内。找到所有不同于第一个像素点的X坐标求平均数,然后结束。

4:计算棋子与目标点的距离,和按压时间。

三、ADB配置

ADB主要是Path配置,配置完,在cmd中输入adb,如下图便是配置成功。需要的可以去http://download.csdn.net/download/thread_cooperation/10212920下载adb配置

四、代码实现

注意:不要在开发工具里跑,可能无法调用ADB。在cmd中是可行的。

 

import java.awt.image.BufferedImage;  
import java.io.BufferedReader;  
import java.io.File;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.Arrays;  
import java.util.concurrent.TimeUnit;  
  
import javax.imageio.ImageIO;  
  
/** 
 * 参考CSDN
 *  @link <a target="_blank" href="http://blog.csdn.net/lihushiwoa/article/details/78942322">http://blog.csdn.net/lihushiwoa/article/details/78942322</a> 
 *  
 * 说明:本版本是在上述参考CSDN的基础上,精简计算而成,目前支持分辨率1920x1080的安卓机(在下没有其他分辨率的手机用于测试)
 *  
 * 跳一跳辅助 
 * @author ykq 
 */  
public class Jump {  
  
    private static final String IMAGE_NAME              = "current.png";  
  
    private static final String STORE_DIR               = "d:/jump_screencapture";  
  
    //截图的数量上限  
    private static final int    imageLengthLength       = 5;  
  
    //存放图片的数组
    private static final long[] imageLength             = new long[imageLengthLength];  
  
    private final RGBInfo       rgbInfo                 = new RGBInfo();  
  
    //adb脚本
    private final String[]      ADB_SCREEN_CAPTURE_CMDS =  
                                                        { "adb shell screencap -p /sdcard/" + IMAGE_NAME,  
            "adb pull /sdcard/current.png " + STORE_DIR };  
  
    //截屏中游戏分数显示区域最下方的Y坐标,300是 1920x1080的值,根据实际情况修改  
    private final int           gameScoreBottomY        = 310;  
  
    //按压的时间系数,可根据具体情况适当调节  
    private final double        pressTimeCoefficient    = 1.37;
    
    //按压的起始点坐标,也是再来一局的起始点坐标  
    private final int           swipeX                  = 550;  
  
    private final int           swipeY                  = 1580;  
  
    //棋子的宽度,从截屏中量取,自行调节  
    private final int           halmaBodyWidth          = 74;

    //csc(pi/6)
    private final static double 		proportion 				= 2/Math.sqrt(3);
    
    public static void main(String[] args){  
        try{  
            File storeDir = new File(STORE_DIR);  //构建文件目录
            if (!storeDir.exists()) {  
               boolean flag = storeDir.mkdir();  //创建文件夹
               if (!flag) {  
                   System.err.println("创建图片存储目录失败");  
                   return;  
               }  
            }  
              
            Jump jumpjumpHelper = new Jump();  
            //执行次数  
            int executeCount = 0;  
            for (;;){  
                //执行ADB命令,获取安卓截屏  
                jumpjumpHelper.executeADBCaptureCommands();  
                File currentImage = new File(STORE_DIR, IMAGE_NAME);  
                if (!currentImage.exists()){  
                    System.out.println("图片不存在");  
                    continue;  
                }  
  
                long length = currentImage.length();  
                imageLength[executeCount % imageLengthLength] = length;  
                //查看是否需要重新开局  
                jumpjumpHelper.checkDoReplay();  
                executeCount++;  
                System.out.println("当前第" + executeCount + "次执行!");  
                //获取跳棋和底板的中心坐标  
                int[] result = jumpjumpHelper.getHalmaAndBoardXYValue(currentImage);   //Halma跳棋
                if (result == null){  
                    System.out.println("The result of method getHalmaAndBoardXYValue is null!");  
                    continue;  
                }  
                int halmaX = result[0];  
                int boardX = result[1];  
                System.out.println("halmaX: " + halmaX + "  ****  " + "boardX: " + boardX);  
                //计算跳跃的距离  
                double jumpDistance = Math.abs(boardX-halmaX)*proportion;
                jumpjumpHelper.doJump(jumpDistance);  
                //每次停留2.5秒  
                TimeUnit.MILLISECONDS.sleep(2500);  
            }  
        }  
        catch (Exception e){  
            e.printStackTrace();  
        }  
    } 
  
    /** 
     * 获取跳棋以及下一块跳板的中心坐标 
     * 
     * @return 
     * @throws IOException 
     */  
    public int[] getHalmaAndBoardXYValue(File currentImage) throws IOException{  
        BufferedImage bufferedImage = ImageIO.read(currentImage);  
        int width = bufferedImage.getWidth();  
        int height = bufferedImage.getHeight();  
        System.out.println("宽度:" + width + ",高度:" + height);  
        int halmaXSum = 0;  
        int halmaXCount = 0;  
        int boardX = 0;  
        //从截屏从上往下逐行遍历像素点,以棋子颜色作为位置识别的依据,最终取出棋子颜色最低行所有像素点的平均值,即计算出棋子所在的坐标  
        for (int y = gameScoreBottomY; y < height; y++){  
            for (int x = 0; x < width; x++){  
                processRGBInfo(bufferedImage, x, y);  //获取指定坐标的RGB值 
                int rValue = this.rgbInfo.getRValue();  
                int gValue = this.rgbInfo.getGValue();  
                int bValue = this.rgbInfo.getBValue();  
                //根据RGB的颜色来识别棋子的位置,  
                if (rValue > 50 && rValue < 60 && gValue > 53 && gValue < 63 && bValue > 95 && bValue < 110){  
                    halmaXSum += x;  
                    halmaXCount++;  
                }  
            }  
        }  
  
        if (halmaXSum != 0 && halmaXCount != 0){  
            //棋子底行的X坐标值  
            int halmaX = halmaXSum / halmaXCount;  
            
            //从gameScoreBottomY开始,获取棋子的x坐标
            for (int y = gameScoreBottomY; y < height; y++){  
                processRGBInfo(bufferedImage, 0, y);  //每行第一个像素的RGB(获取指定坐标的RGB值 )
                int lastPixelR = this.rgbInfo.getRValue();  
                int lastPixelG = this.rgbInfo.getGValue();  
                int lastPixelB = this.rgbInfo.getBValue();  
                //只要计算出来的boardX的值大于0,就表示下个跳板的中心坐标X值取到了。  
                if (boardX > 0){  
                    break;  
                }  
                int boardXSum = 0;  
                int boardXCount = 0;  
                
                for (int x = 0; x < width; x++){  
                    processRGBInfo(bufferedImage, x, y);  
                    int pixelR = this.rgbInfo.getRValue();  
                    int pixelG = this.rgbInfo.getGValue();  
                    int pixelB = this.rgbInfo.getBValue();  
                    //处理棋子头部比下一个跳板还高的情况      跳过棋子所在的x轴区域不遍历(防止检索到棋子的颜色)
                    if (Math.abs(x - halmaX) < halmaBodyWidth){    //abs绝对值
                        continue;  
                    }  
  
                    //从上往下逐行扫描至下一个跳板的顶点位置,下个跳板可能为圆形,也可能为方框,取多个点,求平均值  
                    if ((Math.abs(pixelR - lastPixelR) + Math.abs(pixelG - lastPixelG) + Math.abs(pixelB - lastPixelB)) > 8){   
                        boardXSum += x;  
                        boardXCount++;  
                    }  
                }  
  
                if (boardXSum > 0){  
                    boardX = boardXSum / boardXCount;  
                }  
            }  
  
            if (boardX > 0){  
                int[] result = new int[2];  
                //棋子的X坐标  
                result[0] = halmaX;  
                //下一块跳板的X坐标    
                result[1] = boardX;  
                return result;  
            }  
        }  
  
        return null;  
    }  
  
    /** 
     * 执行命令 
     * 
     * @param command 
     */  
    private void executeCommand(String command){  
        Process process = null;  
        try{  
            process = Runtime.getRuntime().exec(command);  
            System.out.println("exec command start: " + command);  
            process.waitFor();  
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));  
            String line = bufferedReader.readLine();  
            if (line != null){  
                System.out.println(line);  
            }  
            System.out.println("exec command end: " + command);  
        }  
        catch (Exception e){  
            e.printStackTrace();  
        }  
        finally{  
            if (process != null){  
                process.destroy();  
            }  
        }  
    }  
  
    /** 
     * ADB获取安卓截屏 
     */  
    public void executeADBCaptureCommands(){  
        for (String command : ADB_SCREEN_CAPTURE_CMDS)  
        {  
            executeCommand(command);  
        }  
    }  
  
    /** 
     * 跳一下 
     * 
     * @param distance 
     */  
    public void doJump(double distance){  
        System.out.println("distance: " + distance);  
        //计算按压时间,最小200毫秒  
        int pressTime = (int) Math.max(distance * pressTimeCoefficient, 200);  
        System.out.println("pressTime: " + pressTime);  
        //执行按压操作  
        String command = String.format("adb shell input swipe %s %s %s %s %s", swipeX, swipeY, swipeX, swipeY,  
                pressTime);  
        System.out.println(command);  
        executeCommand(command);  
        System.out.println();
        System.out.println();
    }  
  
    /** 
     * 再来一局 
     */  
    private void replayGame(){  
        String command = String.format("adb shell input tap %s %s", swipeX, swipeY);  //adb shell模拟点击事件 input
        executeCommand(command);  
    }  
  
    /** 
     * 检查是否需要重新开局 
     */  
    public void checkDoReplay(){  
        if (imageLength[0] > 0 && imageLength[0] == imageLength[1] && imageLength[1] == imageLength[2]  
                && imageLength[2] == imageLength[3] && imageLength[3] == imageLength[4]){  
            //此时表示已经连续5次图片大小一样了,可知当前屏幕处于再来一局  
            Arrays.fill(imageLength, 0);  
            //模拟点击再来一局按钮重新开局  
            replayGame();  
        }  
    }  
  
    /** 
     * 获取指定坐标的RGB值 
     * 
     * @param bufferedImage 
     * @param x 
     * @param y 
     */  
    private void processRGBInfo(BufferedImage bufferedImage, int x, int y){  
        this.rgbInfo.reset();  
        int pixel = bufferedImage.getRGB(x, y);  
        //转换为RGB数字    
        this.rgbInfo.setRValue((pixel & 0xff0000) >> 16);  
        this.rgbInfo.setGValue((pixel & 0xff00) >> 8);  
        this.rgbInfo.setBValue((pixel & 0xff));  
    }  
  
    
    /**************************  RGBInfo  ****************************/
    class RGBInfo{  
        private int RValue;  
  
        private int GValue;  
  
        private int BValue;  
  
        public int getRValue(){  
            return RValue;  
        }  
  
        public void setRValue(int rValue){ 
            RValue = rValue;  
        }  
  
        public int getGValue(){  
            return GValue;  
        }  
  
        public void setGValue(int gValue){  
            GValue = gValue;  
        }  
  
        public int getBValue(){  
            return BValue;  
        }  
  
        public void setBValue(int bValue){  
            BValue = bValue;  
        }  
  
        public void reset(){  
            this.RValue = 0;  
            this.GValue = 0;  
            this.BValue = 0;  
        }  
    }  
}  

 

<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome/css/font-awesome.min.css"/>
<script src="https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget/autoload.js"></script>

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值