最近坐车刷到一个小游戏,王铲铲的致富之路,就是一个人拿着铁锹,背着背篓挖地,随着级别的提升,可以雇佣更多人一起挖地,挖出的土可以出售给工厂获得收益(游戏货币),货币可以用来升级人物,升级仓库,升级工厂,升级运输队,后面还可以获得一辆挖掘机,加快挖掘速度。
游戏中挖掘机需要加油,这个只能每天领取5桶,再需要加油就得看视频广告来获得了,虚拟货币无法购买。挖掘可以加速,但是也需要看视频广告,看完基本只能看着大家挖土,久而久之,等待很乏味,于是就有了编写个辅助自己玩的想法。
基本思路就是Java调用adb连接手机,查看对应按钮的位置,模拟点击加延时操作,逐渐实现了,连续加速功能,加速结束后,接着点击加速,在等待加速的过程中会出现挖出的古董,可以直接点击捐赠,挖出的土疙瘩,直接关闭页面,这样不影响游戏的流程。
硬件方面:android系统手机一个,电脑(Windows,Linux,Mac均可)一个,usb调试线(wifi也行)
软件方面:jdk8,android8.0,Ubuntu20.04
前置知识:什么是adb,如何使用adb命令
不啰嗦了,直接上代码了。这个类有Java版,参考:链接: Java 调用 ADB 命令截取安卓手机屏幕到PC,详细代码见文章最后。
核心原理
adb shell screencap -p /sdcard/scrsnp.png
# 截屏用时:3231 ms
adb pull /sdcard/scrsnp.png ./capture/a.png
# pull文件用时:215 ms
adb shell rm /sdcard/scrsnp.png
#删除sd卡文件
这个步骤,打开游戏,截屏即可,主要关注右下角的超级加速按钮,广告页面的关闭按钮,底部中间的关闭按钮,古董捐赠按钮,等等按钮的位置,可以参考代码。
为了方便查找触摸坐标,开启了触摸位置显示。
void getColor() {
try {
// 读取图片文件
BufferedImage image = ImageIO.read(
new File("/home/xxx/图片/wcc/screenshotwcc02.png")
);
// 定义需要提取颜色的坐标
int x = 302;
int y = 314;
// 提取坐标颜色
int color = image.getRGB(x, y);
// 打印颜色值
System.out.println("The color at (" + x + ", " + y + ") is: " + color);
int alpha = (color >> 24) & 0xff;
int red = (color >> 16) & 0xff;
int green = (color >> 8) & 0xff;
int blue = color & 0xff;
System.out.printf("argb:%x,%x,%x,%x,", alpha, red, green, blue);
} catch (IOException e) {
e.printStackTrace();
}
}
核心原理如下,Java代码在后面
adb shell input tap $x $y
把这2个类复制到IDE中,运行即可实现功能。
经过实际测试发现,基于固定坐标颜色判定的辅助方式,在关闭土疙瘩和捐赠挖掘到的古董方面还是很稳定的,基本没有遇到错误。
然而在点击加速的时候就很不靠谱了,首先广告有多种:视频、直播、小说;有的时候是分享,并不是看广告,这块处理起来比较麻烦。
有条件的可以试试图片文字识别,这样更稳定一些,还有不太好判断游戏是否在前台,游戏退后台了,不加判断,容易误触。
基于颜色识别准确度不可靠,有时候游戏退后台了,程序还在敬业的点击操作,点击位置已经是驴唇不对马嘴了。而且胡乱点击也比较危险。
后续应该改为图片文字识别,借助Tess4J或者百度ai的开放api,这样更加准确和可靠。下面是代码:Wcc.java
这个类用到了上面的截屏类ScreenCapture.kt。代码比较简陋,基本实现了所需功能,可靠性还有待提升。本文代码取色代码部分参考文心一言。
package learn;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 王铲铲的致富之路Java辅助
*/
public class Wcc {
Color COLOR_TUGEDA = new Color(0xF0, 0xE1, 0x53);
Color COLOR_GUDONG = new Color(0xCC, 0x5E, 0x5B);
Color COLOR_CJJS = new Color(0xE1, 0x90, 0x25);
Color COLOR_JSLQ = new Color(0x60, 0x34, 0x2c);
private int c = 0;
public static void main(String[] args) {
Wcc wcc = new Wcc();
wcc.digHole();
}
/**
* 1.检测当前能不能加速,
* 能
* 则看广告加速
* 不能,
* 看看是图疙瘩还是古董,
* 土疙瘩
* 关闭
* 古董
* 捐赠
*
* 2.if(处理完毕土疙瘩和古董)
* 查看加速是否结束
* 没有,继续等待
* 有,继续加速
*
* 3.(每30s检查一次土疙瘩和古董)
*/
private void digHole() {
//读取设备,准备adb操作。
ScreenCapture capture = new ScreenCapture();
String[] ds = capture.getDevices();
System.out.println("设备列表容量:" + ds.length);
if (ds.length > 0) {
String model = capture.getModel(ds[0]);
System.out.println("手机型号:" + model);
while (true) {
long beginTime = System.currentTimeMillis();
System.out.println("---------------------------------------------");
System.out.println("集中火力干事业,把钱花在刀把上");
System.out.println("第" + (++c) + "次任务开始:" + getDateTime());
capture.snapshot(ds[0], "./capture", "a.png");
cmp(capture);
System.out.println("睡觉去了,30秒后起床");
//睡眠30s,相当于每隔30s处理一下土疙瘩和古董
sleep(30);
//睡眠10分钟,等待加速效果结束
// sleep(60 * 10);
}
}
}
String getDateTime() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// println(formatDateTime)
return now.format(formatter);
}
void cmp(ScreenCapture capture) {
File f = new File("./capture/a.png");
System.out.println(f.toString());
if (f.exists()) {
System.out.println("文件存在");
//加速坐标,超级加速
int jsX = 968;
int jsY = 1819;
Color colorGet = getColor(f.getPath(), jsX, jsY);
double similarity = ColorSimilarity.compare(COLOR_CJJS, colorGet);
System.out.println("超级加速相似度:" + similarity);
//加速冷却按钮,小于0.9表示可点击,否则是冷却状态,不可点击
Color colorGetJslq = getColor(f.getPath(), 448, 1837);
double similarityJslq = ColorSimilarity.compare(COLOR_JSLQ, colorGetJslq);
System.out.println("加速冷却相似度:" + similarityJslq);
if (similarityJslq < 0.9) {
System.out.println("加速可点击,可以点击超级加速");
} else {
System.out.println("加速冷却中……现在不可加速");
}
if (similarity >= 0.90 && similarityJslq < 0.9) {//点击超级加速
System.out.println("超级加速颜色相似度OK,点击超级加速");
/* 这里面没有考虑分享功能,尚待改进 */
/* 还有小说广告、直播广告没有考虑 */
//此时位于游戏主页面,可以点击超级加速
capture.touchXY(903, 1825);
sleep(2);
//点击观看
capture.touchXY(575, 1219);
//等待视频播放结束
sleep(40);
//点击视频完成后的关闭按钮
capture.touchXY(977, 105);
sleep(5);
// 点击放弃奖励按钮(只考虑5分钟的情况)
capture.touchXY(550, 1308);
sleep(5);
// //点击第二次观看
// capture.touchXY(575, 1219);
// //等待视频播放结束
// sleep(40);
// //点击视频完成后的关闭按钮
// capture.touchXY(977, 105);
// sleep(3);
} else {//土疙瘩点击返回,古董点击捐赠,新材料点击知道了
System.out.println("颜色相似度不符,目前不能点击超级加速,看看是土疙瘩还是古董");
//游戏被弹出窗口挡住了
//根据横条的颜色坐标,判断是土疙瘩还是古董
int htX = 302;
int htY = 314;
Color colorGetTG = getColor(f.getPath(), htX, htY);
double similarityTG = ColorSimilarity.compare(COLOR_TUGEDA, colorGetTG);
System.out.println("土疙瘩颜色相似度:" + similarityTG);
if (similarityTG >= 0.9) {//是土疙瘩
System.out.println("是土疙瘩,关闭即可:");
//点击返回按钮关闭土疙瘩
capture.touchXY(76, 121);
sleep(3);
} else {
similarityTG = ColorSimilarity.compare(COLOR_GUDONG, colorGetTG);
System.out.println("古董颜色相似度:" + similarityTG);
if (similarityTG >= 0.9) {//是古董
System.out.println("是古董,点击捐赠按钮");
//点击捐赠按钮
capture.touchXY(376, 1777);
sleep(3);
} else {//意外
System.out.println("不能加速,不是土疙瘩,不是古董,这就奇怪了...莫不是正在加速?");
}
}
}
} else {
System.out.println("截图读取失败,得检查一下问题再继续");
}
}
private static void sleep(int seconds) {
try {
Thread.sleep(1000 * seconds);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Color getColor(String filePath, int x, int y) {
try {
// 读取图片文件
BufferedImage image = ImageIO.read(
new File(filePath)
);
// 提取坐标颜色
int color = image.getRGB(x, y);
// 打印颜色值
System.out.println("The color at (" + x + ", " + y + ") is: " + color);
int alpha = (color >> 24) & 0xff;
int red = (color >> 16) & 0xff;
int green = (color >> 8) & 0xff;
int blue = color & 0xff;
System.out.printf("argb:%x,%x,%x,%x,", alpha, red, green, blue);
Color color2 = new Color(red, green, blue);
return color2;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
package learn
import java.io.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
class ScreenCapture {
val devices: Array<String>
get() {
val command = "adb devices"
println(command)
val devices = ArrayList<String>()
try {
val process = Runtime.getRuntime().exec(command)
val bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
var line = bufferedReader.readLine()
while (line != null) {
println(line)
if (line.endsWith("device")) {
val device = line.substring(0, line.length - "device".length).trim { it <= ' ' }
devices.add(device)
}
line = bufferedReader.readLine()
}
process.destroy()
} catch (e: Exception) {
e.printStackTrace()
}
return devices.toArray(arrayOf())
}
fun getModel(device: String): String? {
val command = "adb -s $device shell getprop"
println(command)
var model: String? = null
try {
val process = Runtime.getRuntime().exec(command)
val bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
var line = bufferedReader.readLine()
while (line != null) {
if (line.contains("[ro.product.model]:")) {
model = line.substring("[ro.product.model]:".length).trim { it <= ' ' }
model = model.substring(1, model.length - 1)
break
}
line = bufferedReader.readLine()
}
process.destroy()
} catch (e: Exception) {
e.printStackTrace()
}
return model
}
fun snapshot(device: String, toPath: String, toFile: String) {
// var f = File(toFile)
// println(f.toString())
// if (f.exists()) {
// var delete = f.delete()
// println("同名文件已经存在,删除它,结果${delete}")
// }
val temp = "scrsnp.png"
val t0 = Date().time
val command1 = "adb -s $device shell screencap -p /sdcard/$temp"
println(command1)
cmdExecute(command1)
val t1 = Date().time
println("截屏用时:${t1 - t0} ms")
val command2 = "adb -s $device pull /sdcard/$temp $toPath/$toFile"
println(command2)
cmdExecute(command2)
val t2 = Date().time
println("pull文件用时:${t2 - t1} ms")
val command3 = "adb -s $device shell rm /sdcard/$temp"
println(command3)
cmdExecute(command3)
val t3 = Date().time
println("删除sd卡图片用时:${t3 - t2} ms")
}
private fun cmdExecute(command: String) {
try {
val process = Runtime.getRuntime().exec(command)
process.waitFor()
process.destroy()
} catch (e: Exception) {
e.printStackTrace()
}
}
fun touchXY(x: Int, y: Int) {
//adb shell input tap $kspx $kspy
var device = devices[0]
var cmd = "adb -s $device shell input tap $x $y"
cmdExecute(cmd)
}
}
基于颜色判断目前,基本满足了需求
package learn;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 王铲铲的致富之路Java辅助
*/
public class Wcc {
Color COLOR_TUGEDA = new Color(0xF0, 0xE1, 0x53);
Color COLOR_GUDONG = new Color(0xCC, 0x5E, 0x5B);
Color COLOR_CJJS = new Color(0xE1, 0x90, 0x25);
Color COLOR_JSLQ = new Color(0x60, 0x34, 0x2c);
Color COLOR_XCL = new Color(0xCC, 0x5E, 0x5B);
private int c = 0;
public static void main(String[] args) {
Wcc wcc = new Wcc();
wcc.digHole();
}
/**
* 1.检测当前能不能加速,
* 能
* 则看广告加速
* 不能,
* 看看是图疙瘩还是古董,
* 土疙瘩
* 关闭
* 古董
* 捐赠
*
* 2.if(处理完毕土疙瘩和古董)
* 查看加速是否结束
* 没有,继续等待
* 有,继续加速
*
* 3.(每30s检查一次土疙瘩和古董)
*/
private void digHole() {
ScreenCapture capture = new ScreenCapture();
String[] ds = capture.getDevices();
System.out.println("设备列表容量:" + ds.length);
if (ds.length > 0) {
String model = capture.getModel(ds[0]);
System.out.println("手机型号:" + model);
while (true) {
long beginTime = System.currentTimeMillis();
System.out.println("---------------------------------------------");
System.out.println("集中火力干事业,把钱花在刀把上");
System.out.println("第" + (++c) + "次任务开始:" + getDateTime());
capture.snapshot(ds[0], "./capture", "a.png");
cmp(capture);
System.out.println("睡觉去了,30秒后起床");
//睡眠30s,相当于每隔30s处理一下土疙瘩和古董
sleep(30);
//睡眠10分钟,等待加速效果结束
// sleep(60 * 10);
}
}
}
String getDateTime() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// println(formatDateTime)
return now.format(formatter);
}
void cmp(ScreenCapture capture) {
File f = new File("./capture/a.png");
System.out.println(f.toString());
if (f.exists()) {
System.out.println("文件存在");
//加速坐标,超级加速
int jsX = 968;
int jsY = 1819;
Color colorGet = getColor(f.getPath(), jsX, jsY);
double similarity = ColorSimilarity.compare(COLOR_CJJS, colorGet);
System.out.println("超级加速相似度:" + similarity);
//加速冷却按钮,小于0.9表示可点击,否则是冷却状态,不可点击
Color colorGetJslq = getColor(f.getPath(), 448, 1837);
double similarityJslq = ColorSimilarity.compare(COLOR_JSLQ, colorGetJslq);
System.out.println("加速冷却相似度:" + similarityJslq);
if (similarityJslq < 0.9) {
System.out.println("加速可点击,可以点击超级加速");
} else {
System.out.println("加速冷却中……现在不可加速");
}
if (similarity >= 0.90 && similarityJslq < 0.9) {//点击超级加速
System.out.println("超级加速颜色相似度OK,点击超级加速");
/* 这里面没有考虑分享功能,尚待改进 */
/* 还有小说广告、直播广告没有考虑 */
// //此时位于游戏主页面,可以点击超级加速
// capture.touchXY(903, 1825);
// sleep(2);
// //点击观看
// capture.touchXY(575, 1219);
// //等待视频播放结束
// sleep(40);
// //点击视频完成后的关闭按钮
// capture.touchXY(977, 105);
// sleep(5);
//
// // 点击放弃奖励按钮(只考虑5分钟的情况)
// capture.touchXY(550, 1308);
// sleep(5);
/begin增强逻辑判断
//此时位于游戏主页面,可以点击超级加速
capture.touchXY(903, 1825);
sleep(2);
//点击观看,然后等待5秒
capture.touchXY(575, 1219);
sleep(5);
//再次截屏判断分享、小说、视频
capture.snapshot(capture.getDevices()[0], "./capture", "b.png");
File fb = new File("./capture/b.png");
// 读取图片文件
BufferedImage image = getBufferedImage(fb.getPath());
//判断顺序,1.分享2.小说3.视频
BufferedImage subimage = image.getSubimage(150, 40, 400, 70);
Color wx = new Color(0xEC, 0xEC, 0xEC);
boolean bWx = hasOnlyExpectedColor(subimage, wx);
Color novel = Color.BLACK;
boolean bNov = hasOnlyExpectedColor(subimage, novel);
System.out.println("是微信分享吗:"+bWx + ", 是小说吗:"+bNov);
if (bWx) {
System.out.println("是微信分享");
//点击微信分享完成后的关闭按钮
capture.touchXY(50, 135);
sleep(5);
// 点击放弃奖励按钮(只考虑5分钟的情况)
capture.touchXY(550, 1308);
sleep(5);
} else if (bNov) {
System.out.println("是小说广告");
//点击小说广告完成后的关闭按钮
capture.touchXY(91, 89);
sleep(5);
// 点击放弃奖励按钮(只考虑5分钟的情况)
capture.touchXY(550, 1308);
sleep(5);
} else { // 视频广告
System.out.println("是视频广告");
//等待视频播放结束,时间缩短,前面休眠时间挺长了
sleep(32);
//点击视频完成后的关闭按钮
capture.touchXY(977, 105);
sleep(5);
// 点击放弃奖励按钮(只考虑5分钟的情况)
capture.touchXY(550, 1308);
sleep(5);
}
/end增强逻辑判断
// //点击第二次观看
// capture.touchXY(575, 1219);
// //等待视频播放结束
// sleep(40);
// //点击视频完成后的关闭按钮
// capture.touchXY(977, 105);
// sleep(3);
} else {//土疙瘩点击返回,古董点击捐赠,新材料点击知道了
System.out.println("颜色相似度不符,目前不能点击超级加速,看看是土疙瘩还是古董");
//游戏被弹出窗口挡住了
//根据横条的颜色坐标,判断是土疙瘩还是古董
int htX = 302;
int htY = 314;
Color colorGetTG = getColor(f.getPath(), htX, htY);
double similarityTG = ColorSimilarity.compare(COLOR_TUGEDA, colorGetTG);
System.out.println("土疙瘩颜色相似度:" + similarityTG);
if (similarityTG >= 0.9) {//是土疙瘩
System.out.println("是土疙瘩,关闭即可:");
//点击返回按钮关闭土疙瘩
capture.touchXY(76, 121);
sleep(3);
} else {
similarityTG = ColorSimilarity.compare(COLOR_GUDONG, colorGetTG);
System.out.println("古董颜色相似度:" + similarityTG);
if (similarityTG >= 0.9) {//是古董
System.out.println("是古董,点击捐赠按钮");
//点击捐赠按钮
capture.touchXY(376, 1777);
sleep(3);
} else {//看看是不是新材料出现了
//根据新材料的条幅位置判断颜色是否是新材料
int newX = 210;
int newY = 400;
Color colorNew = getColor(f.getPath(), newX, newY);
double similarityNew = ColorSimilarity.compare(COLOR_XCL, colorNew);
System.out.println("新材料颜色相似度:" + similarityNew);
if (similarityNew >= 0.9) {
System.out.println("是新材料,点击知道了按钮");
//点击知道了按钮
capture.touchXY(530, 1410);
sleep(3);
} else {//意外
System.out.println("不能加速,不是土疙瘩,不是古董,不是新材料,这就奇怪了...莫不是正在加速?");
}
}
}
}
} else {
System.out.println("截图读取失败,得检查一下问题再继续");
}
}
private static void sleep(int seconds) {
try {
Thread.sleep(1000 * seconds);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Color getColor(String filePath, int x, int y) {
// 读取图片文件
BufferedImage image = getBufferedImage(filePath);
// 提取坐标颜色
int color = image.getRGB(x, y);
// 打印颜色值
System.out.println("The color at (" + x + ", " + y + ") is: " + color);
int alpha = (color >> 24) & 0xff;
int red = (color >> 16) & 0xff;
int green = (color >> 8) & 0xff;
int blue = color & 0xff;
System.out.printf("argb:%x,%x,%x,%x,", alpha, red, green, blue);
Color color2 = new Color(red, green, blue);
return color2;
}
BufferedImage getBufferedImage(String filePath) {
// 读取图片文件
try {
BufferedImage image = ImageIO.read(new File(filePath));
return image;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 图片只包含期望的颜色,用来判断小说、视频、分享
* @param image
* @param expColor
* @return
*/
boolean hasOnlyExpectedColor(BufferedImage image, Color expColor) {
int expectedColor = expColor.getRGB();
int c = 0;
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
int actualColor = image.getRGB(i, j);
if (actualColor != expectedColor) {
System.out.println("The image contains other colors.");
System.out.println("but the found expected color numbers have:" + c);
return false;
} else {
c++;
}
}
}
System.out.println("The image contains only the expected color.");
return true;
}
}