【game】1、pacman利用bfs进行搜索路径自动吃豆
1.设计思路
设计思路有几个,一步步优化来的
v0.1 比较复杂,而且进行了2次bfs,浪费了大量时间
v0.2 简化了2次bfs的操作,但是有很多不必要的判断逻辑,并且考虑不够全
v0.3 极大简化了逻辑,并对幽灵,玩家的路径进行探索
2.编码实现
这里只提供玩家实现,不提供主程序
import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; /** * 人类玩家,使用运行时输入的方式 */ public class CutterPlayer extends PacmanPlayer { private int nextPosition; private final String jarName; // 当前行,列 private int position[] = new int[2]; // 方向 private final static int[][] DIR = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; public static final int BOARD_SIZE = 21; // 地图尺寸 private static final int LEFT = 1; // 左 private static final int UP = 2; // 上 private static final int RIGHT = 3; // 右 private static final int DOWN = 4; // 下 private static final int MY_BARRIER = -999; private static final int DEEP = 21; private static final int CAN_NOT_ARRVIDE = -5; private static final int CAN_NOT_EAT = -5; private static final int CAN_NOT_ARRVIDE_MYSELF = -2; public CutterPlayer(String jarname) throws Exception { this.jarName = jarname; } @Override public void init() { super.init(); nextPosition = 0; } @Override public String getName() { return jarName; } @Override public PlayerType getType() { return PlayerType.JAVA; } @Override public void ready(int no, int timeLimit) throws Exception { position[0] = no / 21; position[1] = no % 21; System.out.println("init[" + position[0] + ":" + position[1] + "]"); } /** * 该接口在游戏中每回合都会被调用一次,用于告知选手程序当前局面,选手程序自行决定行动。 * * @param board 局面的位置列表,假设a行b列(0<=a,b<21),那么对应的值是 21*a+b,当前位置为ghost为-1,为空表示-2 * ,为-3表示大豆子,为-4表示小豆子,为-5、-6、-7表示不同的障碍, * 其余>=0 为该位置的pacman所代表的力量(包括一个自己) * @param strength 当前的力量,由服务器传回来 * @return 方向0代表不动,1,2,3,4 分别代表左、上、右、下,其他输入皆非法 * @throws Exception */ @Override public int run(int[] board, int strength) throws Exception { // board转换为二维数组 boolean haveTarget = false; int[][] erweiboard = new int[BOARD_SIZE][BOARD_SIZE]; for (int i = 0; i < board.length; ++i) { if (board[i] > -5) haveTarget = true; if (isCannotArrivederweiboard(erweiboard, i)) { continue; } // 2.判断是否是幽灵 // 3.判断是否是玩家 // 3.1 玩家力量是否比自己小X if (isGhost(board, i) || isStrongPlayer(board, i, strength)) { setCannotArrived(erweiboard, i); continue; } // 4.计算当前二维数组位置 // 5.填充进入数组 erweiboard[i/21][i%21] = board[i]; } if (!haveTarget) return 0; // 6.获取当前需要递归层数 // 7.添加当前位置加入遍历队列 Queue<Node> nextSteps = new LinkedList<>(); // 广度的队列 boolean[][] flag = new boolean[BOARD_SIZE][BOARD_SIZE]; // 标记 flag[position[0]][position[1]] = true; Node root = new Node(new int[] {position[0], position[1]}); root.curScore = -999; nextSteps.offer(root); // 8.初始化当前层级 int curdeep = 0; Node maxNode = null; boolean init = true; // 9.设置当前最大得分节点为当前节点,分值为-999 // 10.判断队列是否为空&&当前层级<最大递归层级&&最大得分节点不是当前节点 while (!nextSteps.isEmpty() && curdeep <= DEEP && (maxNode != null || init || maxNode.curScore != -2)) { // 11.循环单当前队列长度所有节点 for (int i = 0; i < nextSteps.size(); i++) { Node sonNode = nextSteps.poll(); int[] next = sonNode.value; // 遍历这个点的4个方向 // 12.获取当前循环节点的方向&位置 for (int j = 1; j < 5; j++) { int[] temp = getNextDirection(j, next); // 13.是否遍历过 // 14.是否识别为障碍 if (!flag[temp[0]][temp[1]] && erweiboard[temp[0]][temp[1]] > CAN_NOT_ARRVIDE) { // System.out.println("erweiboard[" +temp[0]+ "][" + temp[1] + "]:" + erweiboard[temp[0]][temp[1]]); // 15.创建新的路径节点 Node node = new Node(temp); node.root = sonNode; node.direction = j; if (erweiboard[temp[0]][temp[1]] == -2) { // 如果是-2 不可取 node.curScore = CAN_NOT_EAT; } else { node.curScore = erweiboard[temp[0]][temp[1]]; } // 16.判断这个节点是否比当前最大得分节点还高 // 16.1 更新最大得分节点 if (maxNode == null || node.curScore > maxNode.curScore) { maxNode = node; init = false; } // 17.加入队列 nextSteps.offer(node); flag[temp[0]][temp[1]] = true; } } } // 18.循环结束,层级++ curdeep++; } // 19.方向并查集向上求得路径最后一个父节点是当前位置的节点 while (maxNode != null && maxNode.root != null && !Arrays.equals(maxNode.root.value, position)) { maxNode = maxNode.root; } // 20.是否为空,为空return 0 if (maxNode == null || Arrays.equals(position, maxNode.value)) return 0; // 21.更新当前坐标 position = getNextDirection(maxNode.direction, position); // 22.返回方向 System.out.println("direction:" + maxNode.direction + "=>[" + position[0] + ":" + position[1] + "], score:" + maxNode.curScore); return maxNode.direction; } public static int[] getNextDirection(int direction, int[] prePosition) { int[] newPosition = new int[] {prePosition[0], prePosition[1]}; switch (direction) { case UP: // 如果是顶部,则跳转到底部,其他方向同理,边界是打通的。 if (prePosition[0] == 0) { newPosition[0] = BOARD_SIZE - 1; } else { newPosition[0] = prePosition[0] - 1; } break; case DOWN: if (prePosition[0] == BOARD_SIZE - 1) { newPosition[0] = 0; } else { newPosition[0] = prePosition[0] + 1; } break; case LEFT: if (prePosition[1] == 0) { newPosition[1] = BOARD_SIZE - 1; } else { newPosition[1] = prePosition[1] - 1; } break; case RIGHT: if (prePosition[1] == BOARD_SIZE - 1) { newPosition[1] = 0; } else { newPosition[1] = prePosition[1] + 1; } break; case 0: break; // 不动代表新位置还是原来的位置 default: // 错误输入 throw new RuntimeException("输入错误"); } return newPosition; } private boolean isCannotArrived(int[] board, int site) { if (board[site] < CAN_NOT_ARRVIDE) { return true; } return false; } private boolean isCannotArrivederweiboard(int[][] erweiboard, int site) { int[] tmpposition = new int[2]; tmpposition[0] = site / 21; tmpposition[1] = site % 21; if (erweiboard[tmpposition[0]][tmpposition[1]] != 0 || Arrays.equals(tmpposition, position)) { return true; } return false; } private boolean isGhost(int[] board, int site) { if (board[site] == -1) { return true; } return false; } private boolean isStrongPlayer(int[] board, int site, int strength) { if (board[site] >= 0 && board[site] >= strength - 2) { return true; } return false; } private void setCannotArrived(int[][] erweiboard, int site) { // 设置4个方向都不可达 int[] prePosition = new int[2]; prePosition[0] = site / 21; prePosition[1] = site % 21; for (int direction = 1; direction <= 4; ++direction) { int[] newPosition = new int[] {prePosition[0], prePosition[1]}; switch (direction) { case UP: // 如果是顶部,则跳转到底部,其他方向同理,边界是打通的。 if (prePosition[0] == 0) { newPosition[0] = BOARD_SIZE - 1; } else { newPosition[0] = prePosition[0] - 1; } break; case DOWN: if (prePosition[0] == BOARD_SIZE - 1) { newPosition[0] = 0; } else { newPosition[0] = prePosition[0] + 1; } break; case LEFT: if (prePosition[1] == 0) { newPosition[1] = BOARD_SIZE - 1; } else { newPosition[1] = prePosition[1] - 1; } break; case RIGHT: if (prePosition[1] == BOARD_SIZE - 1) { newPosition[1] = 0; } else { newPosition[1] = prePosition[1] + 1; } break; case 0: break; // 不动代表新位置还是原来的位置 default: // 错误输入 throw new RuntimeException("输入错误"); } erweiboard[newPosition[0]][newPosition[1]] = MY_BARRIER; erweiboard[prePosition[0]][prePosition[1]] = CAN_NOT_ARRVIDE; } } /** * 用于保存路径 */ public static class Node { private int[] value; private int direction; private Node root; private int curScore = 0; public Node(int[] value) { this.value = value; } @Override public boolean equals(Object obj) { if (obj instanceof Node) { Node other = (Node) obj; return this.value[0] == other.value[0] && this.value[1] == other.value[1]; } return super.equals(obj); } } public int getNextPosition() { return nextPosition; } public void setNextPosition(int nextPosition) { this.nextPosition = nextPosition; } }
实现效果:
黄色为程序吃豆人,红色为外挂吃豆人(只做演示用,或者追杀黄色)