【Game】李连杰黎明双天王代言奇迹MU——自动答题
简介
最近在玩李连杰黎明双天王代言奇迹MU,这个游戏没有官网,广告推广的厉害,乱七八遭一大堆名字,连个官网都没有,这里有个网盘的地址可以下载,是很早以前的版本了,估计现在还能用。
链接:https://pan.baidu.com/s/1MiKi1OFi0CYC3DJOuBzcUg
提取码:bc0r
这个游戏晚上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
注意事项
- 增加模拟器分辨率有960x540改为1280x540,防止上层消息框遮挡题干。
- 将模拟器拖到屏幕左上角对齐(因坐标基于屏幕坐标计算)
- java.awt.Robot控制坐标在某些电脑上可能会有问题,导致鼠标不会按照期望的位置放置,暂时无解。