目录

我的学习分享

记录精彩的程序人生

X

【Game】李连杰黎明双天王代言奇迹MU——自动答题

简介

最近在玩李连杰黎明双天王代言奇迹MU,这个游戏没有官网,广告推广的厉害,乱七八遭一大堆名字,连个官网都没有,这里有个网盘的地址可以下载,是很早以前的版本了,估计现在还能用。

链接:https://pan.baidu.com/s/1MiKi1OFi0CYC3DJOuBzcUg
提取码:bc0r

另一个下载地址:http://dlcs.e836g.com/upload/1_1006231_10913/shenjidalujinglingshengdian-erciguanggaoceshi-toutiao_1060.apk

应用宝下载地址:http://39ej7e.com/freestyle/gzhdownlink/index.html?dl=http://dlcs.320hjd.com/channel_pkg/sapk/1006233/41_1006233.apk

这个游戏晚上7:35有个战盟聚会活动,其中有个答题环节。

项目使用Java完成。

设计思路

通过模拟器在电脑上运行游戏(方便截图和操控鼠标),这里使用夜神模拟器 6.3.0.3
截取答题部分图像(使用java.awt.Robot实现),并通过百度AI识别题干,这里使用通用文字识别的高精度版
自建题库,根据题干在题库中搜索答案
操作鼠标模拟答题操作,输入答案并提交(使用java.awt.Robot实现)

代码

package com.acuilab.qjqa;

import static com.acuilab.qjqa.Main.API_KEY;
import static com.acuilab.qjqa.Main.APP_ID;
import static com.acuilab.qjqa.Main.SECRET_KEY;
import com.baidu.aip.ocr.AipOcr;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 * 20190727
 *  1 增加模拟器分辨率有960*540改为1280*540,防止上层消息框遮挡题干。
 *  2 图像识别改为高精度
 * 
 *
 * @author admin
 */
public class MainJFrame extends javax.swing.JFrame {
    
    private static final int INITIAL_DELAY = 1000;
    private static final int DELAY = 1500;

    private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    
    private List<Pair> qaList = new ArrayList<>();
    
    private Robot robot;
    
    private String prevQuestion;
    
    /**
     * Creates new form MainJFrame
     */
    public MainJFrame() {
        initComponents();
        
        try {
            robot = new Robot();
            // 加载题库
            loadQA();
        } catch (IOException | AWTException ex) {
            ex.printStackTrace();
        }
    }
    
    private void loadQA() throws FileNotFoundException, IOException {
        FileInputStream in = new FileInputStream("qa.properties");
        Properties pro = new Properties();
        pro.load(in);
        for (String key : pro.stringPropertyNames()) {
            System.out.println(key + "=" + pro.getProperty(key));
            qaList.add(new Pair(key, pro.getProperty(key)));
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        logTextArea = new javax.swing.JTextArea();
        stopBtn = new javax.swing.JButton();
        startBtn = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("奇迹问答");

        logTextArea.setEditable(false);
        logTextArea.setColumns(20);
        logTextArea.setLineWrap(true);
        logTextArea.setRows(5);
        logTextArea.setBorder(javax.swing.BorderFactory.createTitledBorder("日志"));
        jScrollPane1.setViewportView(logTextArea);

        stopBtn.setText("停止");
        stopBtn.setEnabled(false);
        stopBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                stopBtnActionPerformed(evt);
            }
        });

        startBtn.setText("开始");
        startBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                startBtnActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 586, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(startBtn)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(stopBtn)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 315, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(stopBtn)
                    .addComponent(startBtn))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    private void startBtnActionPerformed(java.awt.event.ActionEvent evt) {                                         
        if(executorService.isShutdown()) {
            executorService = Executors.newSingleThreadScheduledExecutor();
        }
     
        executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    
                    
                    // 捕获图像并识别
                    JSONObject json = captureAndDetect();
                    
                    JSONArray jsonArray = json.getJSONArray("words_result");
                    
                    StringBuilder sb = new StringBuilder();
                    for(int i=0; i<jsonArray.length(); i++) {
                        JSONObject obj = jsonArray.getJSONObject(i);
                        String words = obj.getString("words");
                        sb.append(words);
                    }
                    
                    String question = sb.toString();
                    if(StringUtils.equals(prevQuestion, question)) {
                        // 相同题目,忽略
                        SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    logTextArea.append("题干:相同题目,忽略\n");
                                    logTextArea.append("————————————————————————————\n");
                                }
                        });
                        return;
                    }
                    SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                logTextArea.append("题干:");
                                 logTextArea.append(question);
                                 logTextArea.append("\n");
                            }
                    });
                    
                    // 从题库中查找答案
                    String answer = "";
                    for(Pair pair : qaList) {
                        if(StringUtils.contains(question, pair.getKey())) {
                            // 找到答案
                            answer = pair.getValue();
                            break;
                        }
                    }
                    
                    if(StringUtils.isBlank(answer)) {
                        // 未找到答案,忽略
                        SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    logTextArea.append("答案:未找到,忽略\n");
                                    logTextArea.append("————————————————————————————\n");
                                }
                        });
                        return;
                    }
                    final String finalAnswer = answer;
                    SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                logTextArea.append("答案:");
                                logTextArea.append(finalAnswer);
                                
                                logTextArea.append("\n");
                                logTextArea.append("————————————————————————————\n");
                                
                                logTextArea.setCaretPosition(logTextArea.getText().length());
                                
                                // 输入答案
                                inputAnswer(finalAnswer);
//                                inputAnswer("Hello!");
                            }
                    });
                } catch (IOException | AWTException ex) {
                    ex.printStackTrace();
                }
            }
            
        }, INITIAL_DELAY, DELAY, TimeUnit.MILLISECONDS);
        
        startBtn.setEnabled(false);
        stopBtn.setEnabled(true);
    }                                        

    private void stopBtnActionPerformed(java.awt.event.ActionEvent evt) {                                        
        executorService.shutdown();
        
        stopBtn.setEnabled(false);
        startBtn.setEnabled(true);
    }                                       

    private void inputAnswer(String answer) {
        // ————输入答案————
        // 1 鼠标单击输入框
        robot.mouseMove(150, 540);
        robot.mousePress(KeyEvent.BUTTON1_MASK);
        robot.mouseRelease(KeyEvent.BUTTON1_MASK);
        robot.delay(200);

        // 2 发现有时文本粘贴不上,可能是失去了焦点,这里再点击一次
        robot.mouseMove(150, 530);
        robot.mousePress(KeyEvent.BUTTON1_MASK);
        robot.mouseRelease(KeyEvent.BUTTON1_MASK);
        robot.delay(50);
        
        // 3 使用剪贴板完成文本输入
        StringSelection stringSelection = new StringSelection(answer);
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(stringSelection, stringSelection);
        robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_V);
        robot.keyRelease(KeyEvent.VK_V);
        robot.keyRelease(KeyEvent.VK_CONTROL);
        robot.delay(250);

        // 4 单击确定
        robot.mouseMove(1220, 530);
        robot.mousePress(KeyEvent.BUTTON1_MASK);
        robot.mouseRelease(KeyEvent.BUTTON1_MASK);
        robot.delay(250);

        // 4 单击发送
        robot.mouseMove(430, 540);
        robot.mousePress(KeyEvent.BUTTON1_MASK);
        robot.mouseRelease(KeyEvent.BUTTON1_MASK);
        robot.delay(250);
    }
    
    private JSONObject captureAndDetect() throws IOException, AWTException {
        // 先捕获一张图片保存到本地
        Image imageSaved = robot.createScreenCapture(new Rectangle(76, 32, 412, 486));
        ImageIO.write((RenderedImage)imageSaved, "png", new File("D:/qj/" + System.currentTimeMillis() + ".png"));
        
        Image image = robot.createScreenCapture(new Rectangle(120, 84, 336, 60));
        ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
        ImageIO.write((RenderedImage) image, "jpg", bos);
        byte[] file = bos.toByteArray();
        
        // 初始化一个AipOcr
        AipOcr client = new AipOcr(APP_ID, API_KEY, SECRET_KEY);

//        // 可选:设置网络连接参数
//        client.setConnectionTimeoutInMillis(2000);
//        client.setSocketTimeoutInMillis(60000);

//        // 可选:设置代理服务器地址, http和socket二选一,或者均不设置
//        client.setHttpProxy("proxy_host", proxy_port);  // 设置http代理
//        client.setSocketProxy("proxy_host", proxy_port);  // 设置socket代理

//        // 可选:设置log4j日志输出格式,若不设置,则使用默认配置
//        // 也可以直接通过jvm启动参数设置此环境变量
//        System.setProperty("aip.log4j.conf", "path/to/your/log4j.properties");

        // 调用接口(通用文字识别(高精度版))
        return client.basicAccurateGeneral(file, new HashMap<String, String>());
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(MainJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(MainJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(MainJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(MainJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new MainJFrame().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextArea logTextArea;
    private javax.swing.JButton startBtn;
    private javax.swing.JButton stopBtn;
    // End of variables declaration                   
}

项目地址:https://github.com/acuilab/qjqa

注意事项

  1. 增加模拟器分辨率有960x540改为1280x540,防止上层消息框遮挡题干。
  2. 将模拟器拖到屏幕左上角对齐(因坐标基于屏幕坐标计算)
  3. java.awt.Robot控制坐标在某些电脑上可能会有问题,导致鼠标不会按照期望的位置放置,暂时无解。