背景
前段时间,闲暇时间玩了微信中的一款游戏叫 最强连一连,玩了一段时间发现手动去玩不知道要玩几个月,于是就开始各种找资料。找了几个代码,最终找到这个 大神的代码,他写了一篇文章叫 《微信小程序 最强连一连攻略 程序自动玩》,折腾了一番下来我也成功的刷完了全部关卡。
准备工作
- 安卓手机一台
- 电脑安装好 ADB
细节记录
- 先用安卓手机原装线连接PC,安装好对应的驱动。说到这里可能会有很多坑,我手上这台手机是 小米8 se,安装驱动费了小半天的时间找。
- 在PC上安装ADB,配置好对应的环境变量。
配置完成之后,打开cmd 输入 adb version
3. 在cmd 中输入 adb devices , 如果一切正常这里会显示你的devices的设备号。
如果找不到,请参考以下这个链接。
adb devices找不到设备?设备VID缺失解决方案
开始自动玩游戏
手机:小米8 se
手机分辨率:2244 * 1080
电脑系统: windows7 64位
最开始用代码去玩游戏的时候,需要看原来博客中的一张图
为什么要看这张图呢,图中的 W 和 h 都是需要设置的,而且不同的关卡,这个值会变化。
说到这里我们需要关注四个值,W 和 h , 几行,几列
两列之间的值,两行之间的值。
一般情况,两列之间的值 = 两行之间的值。
部分代码如下
public final static char BAN = ' ', EMPTY = '□', EXIST = '■';
// private static int startH = 537, startW = 170, offsetH = 145, offsetW = 145, rowSize = 8, colSize = 6; 21级 52 关
private static int startH = 541, startW = 170, offsetH = 145, offsetW = 145, rowSize = 7, colSize = 6;
// 自动玩
public static void autoPlay() {
try {
if (Files.notExists(Paths.get("D:/link")))
Files.createDirectory(Paths.get("D:/link"));
} catch (IOException e) {
e.printStackTrace();
}
while (true) {
long startTime = System.currentTimeMillis();
if (screenshot()) {
GameStatus gameStatus = analysisScreenshot();
if (gameStatus != null) {
NodeTree nodeTree = new NodeTree(gameStatus.getStatus(), gameStatus.getCount(), gameStatus.getStartRow(), gameStatus.getStartCol());
Node[] nodes = nodeTree.DFS();
if (nodes != null) {
play(nodes);
nextGame();
} else {
System.out.println("游戏搜索失败,重新开始");
}
} else {
System.out.println("游戏读取失败,重新开始");
}
System.out.println("耗费总时长:" + (System.currentTimeMillis() - startTime) + "毫秒\n");
}
}
}
从以上代码,可以知道代码中 private static int startH = 541, startW = 170, offsetH = 145, offsetW = 145, rowSize = 7, colSize = 6;
startH : 画面顶部到第一行中间位置的高度
startW :画面左侧到第一列中金位置的宽度
offsetH :两列之间的值
offsetW :两行之间的值
rowSize :画面中的行数
colSize : 画面中的列数
以上的值是随着关卡变化而变化的,据我观察一般20-50关之内变化不大。
另外一点,需要说明一下,手机的分辨率也会影响游戏画面的呈现,比如用我的手机打开29级52关的时候两边格子只有显示一半了。
另外还有一个问题,相同的代码在windows7 下运行会一直耗内存,本机电脑16G ,代码一直运行的时候会吃内存吃到 10.5G 左右就不在吃内存了。
而把代码放在windows10 里面运行,丝毫没有吃内存的迹象,关于这点我也不知道怎么解释问了 代码原作者也表示不清楚,不过并不影响刷游戏…
在刷的过程中,连接手机之后建议使用 传输文件 模式 (不使用这个模式,后来证实也是可以的)
源码部分
以下代码 均为原作者所有
NodeUtil.java
package com.example.lianlian;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.imageio.ImageIO;
/**
* @Auther: Administrator
* @Date: 2019-02-22
* @Description:
*/
public class NodeUtil {
public final static char BAN = ' ', EMPTY = '□', EXIST = '■';
// private static int startH = 537, startW = 170, offsetH = 145, offsetW = 145, rowSize = 8, colSize = 6; 21级 52 关
private static int startH = 541, startW = 170, offsetH = 145, offsetW = 145, rowSize = 7, colSize = 6;
// 自动玩
public static void autoPlay() {
try {
if (Files.notExists(Paths.get("D:/link")))
Files.createDirectory(Paths.get("D:/link"));
} catch (IOException e) {
e.printStackTrace();
}
while (true) {
long startTime = System.currentTimeMillis();
if (screenshot()) {
GameStatus gameStatus = analysisScreenshot();
if (gameStatus != null) {
NodeTree nodeTree = new NodeTree(gameStatus.getStatus(), gameStatus.getCount(), gameStatus.getStartRow(), gameStatus.getStartCol());
Node[] nodes = nodeTree.DFS();
if (nodes != null) {
play(nodes);
nextGame();
} else {
System.out.println("游戏搜索失败,重新开始");
}
} else {
System.out.println("游戏读取失败,重新开始");
}
System.out.println("耗费总时长:" + (System.currentTimeMillis() - startTime) + "毫秒\n");
}
}
}
// 截屏到电脑
private static boolean screenshot() {
try {
Runtime.getRuntime().exec("adb shell /system/bin/screencap -p /sdcard/most_link_link.png").waitFor();
Runtime.getRuntime().exec("adb pull /sdcard/most_link_link.png D:/link").waitFor();
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
// 分析游戏状态
public static GameStatus analysisScreenshot() {
GameStatus gameStatus = new GameStatus();
char[][] status = new char[rowSize][colSize];
int count = 0, startPoint = 0;
try {
BufferedImage screenshot = ImageIO.read(new File("D:/link/most_link_link.png"));
// Graphics graphics = screenshot.getGraphics();
// graphics.setColor(Color.red);
// graphics.setFont(new Font("华文行楷", Font.BOLD, 100));
for (int h = startH, rowIndex = 0; rowIndex < status.length && startPoint <= 1; h += offsetH, rowIndex++) {
for (int w = startW, colIndex = 0; colIndex < status[rowIndex].length && startPoint <= 1; w += offsetW, colIndex++) {
int rgb = screenshot.getRGB(w, h);
// graphics.drawString(".", w, h);
if (rgb == -3355444) { // -3355444灰色
status[rowIndex][colIndex] = EMPTY;
count++;
} else if (rgb != -14472389) { // -14472389背景色
status[rowIndex][colIndex] = EXIST;
count++;
gameStatus.setStartRow(rowIndex);
gameStatus.setStartCol(colIndex);
startPoint++;
} else {
status[rowIndex][colIndex] = BAN;
}
}
}
// ImageIO.write(screenshot, "png", new FileOutputStream("G:/link/draw_point.png"));
gameStatus.setCount(count);
gameStatus.setStatus(status);
} catch (IOException e) {
e.printStackTrace();
} finally {
printStatus(status);
}
return startPoint == 1 ? gameStatus : null;
}
// 点击关卡灰格
private static void play(Node[] nodes) {
try {
for (int i = 1; i < nodes.length; i++) {
int j = i;
while (j + 1 < nodes.length && (nodes[i].getRow() == nodes[j + 1].getRow() || nodes[i].getCol() == nodes[j + 1].getCol())) {
j++;
}
String command = String.format("adb shell input swipe %d %d %d %d", nodes[i].getCol() * offsetW + startW, nodes[i].getRow() * offsetH + startH, nodes[j].getCol() * offsetW + startW, nodes[j].getRow() * offsetH + startH);
System.out.println(command);
Runtime.getRuntime().exec(command).waitFor();
i = j;
}
//Thread.sleep(300);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 下一关
private static void nextGame() {
try {
//Runtime.getRuntime().exec("adb shell input tap 929 660").waitFor(); // 关闭双倍奖励
//Runtime.getRuntime().exec("adb shell input tap 540 1600").waitFor(); // 下一关
Runtime.getRuntime().exec("adb shell input tap 925 822").waitFor(); // 关闭双倍奖励
Runtime.getRuntime().exec("adb shell input tap 540 1700").waitFor(); // 下一关
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印盘面状态
public static void printStatus(char[][] status) {
for (int row = 0; row < status.length; row++) {
for (int col = 0; col < status[row].length - 1; col++) {
System.out.format("%c ", status[row][col]);
}
System.out.format("%c%n", status[row][status[row].length - 1]);
}
}
/* public static void printStatus(char[][] status) {
for (int row = 0; row < status.length; row++) {
for (int col = 0; col < status[row].length; col++) {
System.out.print(status[row][col]);
if (col != status[row].length - 1) {
System.out.print(" ");
}
}
System.out.println();
}
}*/
private static class GameStatus {
private char[][] status;
private int count, startRow, startCol; // count方格数量
public char[][] getStatus() {
return status;
}
public void setStatus(char[][] status) {
this.status = status;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getStartRow() {
return startRow;
}
public void setStartRow(int startRow) {
this.startRow = startRow;
}
public int getStartCol() {
return startCol;
}
public void setStartCol(int startCol) {
this.startCol = startCol;
}
}
}
NodeTree.java
package com.example.lianlian;
/**
* @Auther: Administrator
* @Date: 2019-02-22
* @Description:
*/
public class NodeTree {
private char[][] status;
private Node[] nodes;
private int count, nodesIndex, sum; // count方格数量,sum搜索的节点数量
public NodeTree(char[][] status, int count, int startRow, int startCol) {
this.status = status;
this.count = count;
this.nodes = new Node[count];
Node root = new Node();
root.setRow(startRow);
root.setCol(startCol);
this.nodes[nodesIndex] = root;
this.sum++;
this.count--;
}
private int[][] directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; // 上、右、下、左
// 深度优先搜索
public Node[] DFS() {
long startTime = System.currentTimeMillis();
OVER:
while (nodes[0].getDirectionCount() < directions.length) {
Node parent = nodes[nodesIndex];
int direction = parent.getDirection();
while (true) {
if (nodesIndex > 0 && parent.getDirectionCount() >= directions.length) { // 节点改变方向次数大于等于4次,即4个方向均尝试过,回退一个节点
status[parent.getRow()][parent.getCol()] = NodeUtil.EMPTY;
count++;
parent = nodes[--nodesIndex]; // 回退一个节点
parent.setDirection((parent.getDirection() + 1) % directions.length); // 回退的节点改变方向
parent.setDirectionCount(parent.getDirectionCount() + 1);
break;
}
Node child = nextStep(parent, direction);
if (child != null) {
nodes[++nodesIndex] = parent = child;
sum++;
if (--count == 0) {
System.out.format("搜索时间:%d毫秒, 搜索的节点数量:%d%n", System.currentTimeMillis() - startTime, sum);
break OVER;
}
} else {
direction = (direction + 1) % directions.length;
parent.setDirection(direction);
parent.setDirectionCount(parent.getDirectionCount() + 1);
}
}
}
return count == 0 ? nodes : null;
}
// 走一步
private Node nextStep(Node parent, int direction) {
int childRow = parent.getRow() + directions[direction][0];
int childCol = parent.getCol() + directions[direction][1];
if (childRow < 0 || childRow >= status.length || childCol < 0 || childCol >= status[0].length || status[childRow][childCol] != NodeUtil.EMPTY) {
return null;
}
Node child = nodes[nodesIndex + 1]; // 之前丢弃的节点重新利用,减少new的次数
if (child != null) {
child.setDirectionCount(0);
} else {
child = new Node();
}
child.setRow(childRow);
child.setCol(childCol);
child.setDirection(direction);
status[childRow][childCol] = NodeUtil.EXIST;
return child;
}
}
Node.java
package com.example.lianlian;
/**
* @Auther: Administrator
* @Date: 2019-02-22
* @Description:
*/
public class Node {
private int row, col, direction, directionCount; // directionCount累计节点改变方向次数
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
public int getDirectionCount() {
return directionCount;
}
public void setDirectionCount(int directionCount) {
this.directionCount = directionCount;
}
}
MostLinkLinkTest.java
package com.example.lianlian;
/**
* @Auther: Administrator
* @Date: 2019-02-22
* @Description:
*/
public class MostLinkLinkTest {
public static void main(String[] args) {
//NodeUtil.screenshot();
NodeUtil.autoPlay();
}
}
更多游戏解析代码,可以关注 这个大神
https://blog.csdn.net/lcl1997
以上就是所有的代码了,如果不明白的地方可以加入我的Q群: 816175200