程序将手动玩游戏的过程分四步完成
将手机屏幕截屏保存到电脑
// 截屏到电脑
private static boolean screenshot() {
try {
runtime.exec("adb shell /system/bin/screencap -p /sdcard/most_link_link.png").waitFor();
runtime.exec("adb pull /sdcard/most_link_link.png G:/link").waitFor();
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
即读取截屏相片的RGB值,根据RGB值区分灰格、起点的行、列
// 分析游戏状态
private 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("G:/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;
}
使用广度、深度优先搜索都可以,但是游戏没有最优解的情况,所以程序使用深度优先搜索
// 深度优先搜索
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 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.exec(command).waitFor();
i = j;
}
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Node.java
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;
}
}
NodeTree.java
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;
}
}
NodeUtil.java
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.imageio.ImageIO;
public class NodeUtil {
public final static char BAN = ' ', EMPTY = '□', EXIST = '■';
private final static Runtime runtime = Runtime.getRuntime();
private final static int startH = 420, startW = 210, offsetH = 130, offsetW = 130, rowSize = 7, colSize = 6;
// 自动玩
public static void autoPlay() {
boolean proceed = true;
try {
if (Files.notExists(Paths.get("G:/link")))
Files.createDirectory(Paths.get("G:/link"));
while (proceed) {
long startTime = System.currentTimeMillis();
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.format("耗费总时长:%d毫秒%n", System.currentTimeMillis() - startTime);
}
} catch (Exception e) {
e.printStackTrace();
proceed = false;
}
}
// 截屏到电脑
private static void screenshot() throws Exception {
runtime.exec("adb shell screencap /sdcard/most_link_link.png").waitFor();
runtime.exec("adb pull /sdcard/most_link_link.png G:/link").waitFor();
}
// 分析游戏状态
private static GameStatus analysisScreenshot() throws Exception {
GameStatus gameStatus = new GameStatus();
char[][] status = new char[rowSize][colSize];
int count = 0, startPoint = 0;
BufferedImage screenshot = ImageIO.read(new File("G:/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);
printStatus(status);
return startPoint == 1 ? gameStatus : null;
}
// 触摸滑动灰格
private static void play(Node[] nodes) throws Exception {
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.exec(command).waitFor();
i = j;
}
Thread.sleep(300);
}
// 下一关
private static void nextGame() throws Exception {
runtime.exec("adb shell input tap 929 660").waitFor(); // 关闭双倍奖励
runtime.exec("adb shell input tap 540 1420").waitFor(); // 下一关
}
// 打印盘面状态
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]);
}
}
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;
}
}
}
MostLinkLinkTest.java
public class MostLinkLinkTest {
public static void main(String[] args) {
// -------------------------------------------测试使用-----------------------------------------------------
// char[][] status = {
// {NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EXIST, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.BAN},
// {NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EMPTY},
// {NodeUtil.BAN, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.BAN, NodeUtil.EMPTY},
// {NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.BAN, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EMPTY},
// {NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.EMPTY, NodeUtil.BAN, NodeUtil.EMPTY, NodeUtil.EMPTY}
// };
// int count = 25, startRow = 0, startCol = 2;
// NodeTree nodeTree = new NodeTree(status, count, startRow, startCol);
// nodeTree.DFS();
// --------------------------------------------------------------------------------------------------------
NodeUtil.autoPlay();
}
}
每关的行数、列数不是一样的,需要修改rowSize、colSize、startH、startW、offsetH、offsetW的值
关闭双倍奖励和下一关的点击位置也会因为手机屏幕适配问题也要做相应的修改
最后愉快的冲向排行榜第一名