每日算法 — 使用java实现老鹰捉小鸡:追逐算法与最短拦截路径规划

老鹰捉小鸡是经典的儿童追逐游戏,老鹰需要绕过母鸡的拦截去抓捕小鸡。本文用Java实现这一游戏的AI决策系统,重点讲解追逐算法中的视线追逐、最短拦截路径计算,以及基于A*搜索和BFS的路径规划策略。

问题建模

将游戏场地抽象为二维网格地图:
– 老鹰(Eagle):追逐者,每回合可移动1格,目标是抓捕到小鸡
– 母鸡(Hen):拦截者,每回合可移动1格,阻挡老鹰与小鸡之间的路径
– 小鸡(Chicks):被追逐者,每回合可移动1格,逃离老鹰

核心问题是:在动态环境中,老鹰如何计算最优追逐路径,同时避开母鸡的拦截?

核心算法

1. A*寻路 + 动态障碍物

A*算法用于计算从老鹰当前位置到目标小鸡的最短路径,同时将母鸡及其影响范围视为动态障碍物。

2. 拦截点预测

预判母鸡的移动意图,计算老鹰是否能在母鸡到达拦截位置之前完成抓捕。

3. 多目标决策

当存在多只小鸡时,老鹰需要选择最优目标——综合考虑距离、母鸡拦截难度和逃生方向。

完整Java实现

import java.util.*;

/**
 * 老鹰捉小鸡 — 追逐算法与最短拦截路径规划
 * 基于A*搜索的动态路径规划与多目标决策系统
 */
public class EagleChaseGame {

    // 地图尺寸
    private final int width;
    private final int height;
    // 四方向移动
    private static final int[][] DIRS = {{-1,0}, {1,0}, {0,-1}, {0,1}};

    public EagleChaseGame(int width, int height) {
        this.width = width;
        this.height = height;
    }

    /**
     * A*寻路算法:计算从start到goal的最短路径
     * 将母鸡位置及其邻接格视为高代价区域(而非完全阻塞)
     */
    public List<int[]> astarSearch(int[] start, int[] goal,
                                    List<int[]> henPositions,
                                    Set<String> obstacles) {
        PriorityQueue<Node> open = new PriorityQueue<>(Comparator.comparingInt(n -> n.f));
        Map<String, Node> allNodes = new HashMap<>();

        Node startNode = new Node(start[0], start[1], 0, heuristic(start, goal), null);
        open.add(startNode);
        allNodes.put(key(start), startNode);

        Set<String> closed = new HashSet<>();

        while (!open.isEmpty()) {
            Node current = open.poll();
            String curKey = key(current.x, current.y);

            if (closed.contains(curKey)) continue;
            closed.add(curKey);

            // 到达目标
            if (current.x == goal[0] && current.y == goal[1]) {
                return reconstructPath(current);
            }

            // 扩展邻居
            for (int[] d : DIRS) {
                int nx = current.x + d[0];
                int ny = current.y + d[1];
                String nKey = key(nx, ny);

                if (!inBounds(nx, ny)) continue;
                if (obstacles.contains(nKey)) continue;
                if (closed.contains(nKey)) continue;

                // 代价计算:基础代价 + 母鸡拦截惩罚
                int moveCost = 1;
                for (int[] hen : henPositions) {
                    int distToHen = Math.abs(nx - hen[0]) + Math.abs(ny - hen[1]);
                    if (distToHen <= 1) {
                        moveCost += 5; // 紧邻母鸡代价极高
                    } else if (distToHen <= 2) {
                        moveCost += 2; // 附近母鸡有额外代价
                    }
                }

                int newG = current.g + moveCost;
                Node neighbor = allNodes.get(nKey);

                if (neighbor == null || newG < neighbor.g) {
                    neighbor = new Node(nx, ny, newG,
                                       newG + heuristic(new int[]{nx, ny}, goal), current);
                    allNodes.put(nKey, neighbor);
                    open.add(neighbor);
                }
            }
        }

        return Collections.emptyList(); // 无路径
    }

    /**
     * 计算拦截点:母鸡应该在哪些位置设置拦截
     * 基于"老鹰到小鸡的最短路径"计算拦截效率最高的点
     */
    public List<int[]> computeInterceptionPoints(int[] eagle, int[] chick,
                                                  List<int[]> hens,
                                                  Set<String> obstacles) {
        // 先计算老鹰到小鸡的最短路径(忽略母鸡)
        List<int[]> directPath = astarSearch(eagle, chick, Collections.emptyList(), obstacles);
        if (directPath.isEmpty()) return Collections.emptyList();

        List<int[]> interceptionPoints = new ArrayList<>();

        // 对路径上的每个点,评估母鸡能否及时到达
        for (int i = 0; i < directPath.size(); i++) {
            int[] pathPoint = directPath.get(i);

            for (int[] hen : hens) {
                // 母鸡到该拦截点的距离
                int henDist = Math.abs(hen[0] - pathPoint[0]) + Math.abs(hen[1] - pathPoint[1]);
                // 老鹰到该点的步数
                int eagleSteps = i;

                // 如果母鸡能在老鹰之前或同时到达,则该点是有效拦截点
                if (henDist <= eagleSteps) {
                    interceptionPoints.add(new int[]{pathPoint[0], pathPoint[1], henDist, eagleSteps});
                }
            }
        }

        // 按拦截效率排序(母鸡越早到达越好)
        interceptionPoints.sort((a, b) -> {
            double effA = (double) a[3] / (a[2] + 1);
            double effB = (double) b[3] / (b[2] + 1);
            return Double.compare(effB, effA);
        });

        return interceptionPoints;
    }

    /**
     * 老鹰目标选择:评估每只小鸡的"抓捕价值"
     * 综合考虑距离、母鸡密度、逃跑方向
     */
    public int[] selectBestTarget(int[] eagle,
                                   List<int[]> chicks,
                                   List<int[]> hens,
                                   Set<String> obstacles) {
        double bestScore = Double.NEGATIVE_INFINITY;
        int[] bestTarget = null;

        for (int[] chick : chicks) {
            // 路径长度(距离)
            List<int[]> path = astarSearch(eagle, chick, hens, obstacles);
            if (path.isEmpty()) continue;
            int distance = path.size();

            // 母鸡拦截密度:路径上母鸡影响范围
            int henDensity = 0;
            for (int[] point : path) {
                for (int[] hen : hens) {
                    if (Math.abs(point[0] - hen[0]) + Math.abs(point[1] - hen[1]) <= 2) {
                        henDensity++;
                    }
                }
            }

            // 逃跑空间评估:小鸡周围的空旷区域
            int escapeSpace = countEscapeSpace(chick, hens, obstacles);

            // 综合评分:距离近 + 拦截少 + 逃跑空间小 = 高价值
            double score = -distance * 3.0 - henDensity * 2.0 - escapeSpace * 0.5;

            if (score > bestScore) {
                bestScore = score;
                bestTarget = chick;
            }
        }

        return bestTarget;
    }

    /**
     * 小鸡逃生策略:选择使得到老鹰距离最大化,同时靠近母鸡的方向
     */
    public int[] chickEscapeMove(int[] chick, int[] eagle,
                                  List<int[]> hens, Set<String> obstacles) {
        int[] bestMove = null;
        double bestScore = Double.NEGATIVE_INFINITY;

        for (int[] d : DIRS) {
            int nx = chick[0] + d[0];
            int ny = chick[1] + d[1];

            if (!inBounds(nx, ny) || obstacles.contains(key(nx, ny))) continue;

            // 到新位置后与老鹰的距离
            int distToEagle = Math.abs(nx - eagle[0]) + Math.abs(ny - eagle[1]);
            // 到最近母鸡的距离(越近越安全)
            int minDistToHen = Integer.MAX_VALUE;
            for (int[] hen : hens) {
                int dth = Math.abs(nx - hen[0]) + Math.abs(ny - hen[1]);
                minDistToHen = Math.min(minDistToHen, dth);
            }

            // 评分:远离老鹰 + 靠近母鸡
            double score = distToEagle * 2.0 - minDistToHen * 1.5;

            if (score > bestScore) {
                bestScore = score;
                bestMove = new int[]{nx, ny};
            }
        }

        return bestMove != null ? bestMove : chick;
    }

    /**
     * 母鸡拦截策略:移动到最佳拦截位置
     */
    public int[] henInterceptMove(int[] hen, int[] eagle, int[] targetChick,
                                   Set<String> obstacles) {
        // 计算老鹰到小鸡的最短路径
        List<int[]> eaglePath = astarSearch(eagle, targetChick,
                                             Collections.emptyList(), obstacles);
        if (eaglePath.isEmpty()) return hen;

        // 寻找路径上母鸡能最先到达的拦截点
        int[] bestIntercept = null;
        int bestPriority = Integer.MAX_VALUE;

        for (int i = 1; i < eaglePath.size(); i++) {
            int[] point = eaglePath.get(i);
            int dist = Math.abs(hen[0] - point[0]) + Math.abs(hen[1] - point[1]);
            // 优先级:能拦截且越早越好
            if (dist <= i && dist < bestPriority) {
                bestPriority = dist;
                bestIntercept = point;
            }
        }

        if (bestIntercept == null) {
            // 无法拦截,尝试靠近小鸡
            bestIntercept = targetChick;
        }

        // 向拦截点移动一步(A*一步)
        List<int[]> movePath = astarSearch(hen, bestIntercept,
                                            Collections.emptyList(), obstacles);
        return movePath.size() > 1 ? movePath.get(1) : hen;
    }

    /**
     * 游戏主循环:模拟一回合
     */
    public GameState simulateTurn(GameState state) {
        // 老鹰选择目标
        int[] target = selectBestTarget(state.eagle, state.chicks, state.hens, state.obstacles);

        if (target == null) return state; // 无目标

        // 母鸡移动(拦截)
        List<int[]> newHens = new ArrayList<>();
        for (int[] hen : state.hens) {
            int[] move = henInterceptMove(hen, state.eagle, target, state.obstacles);
            newHens.add(move);
        }

        // 小鸡移动(逃跑)
        List<int[]> newChicks = new ArrayList<>();
        for (int[] chick : state.chicks) {
            // 检查是否已被抓捕
            if (chick[0] == state.eagle[0] && chick[1] == state.eagle[1]) {
                continue; // 已被抓捕,移除
            }
            int[] move = chickEscapeMove(chick, state.eagle, newHens, state.obstacles);
            newChicks.add(move);
        }

        // 老鹰移动(追逐)
        List<int[]> eaglePath = astarSearch(state.eagle, target, newHens, state.obstacles);
        int[] newEagle = eaglePath.size() > 1 ? eaglePath.get(1) : state.eagle;

        return new GameState(newEagle, newHens, newChicks, state.obstacles);
    }

    // 辅助:计算逃跑空间
    private int countEscapeSpace(int[] pos, List<int[]> hens, Set<String> obstacles) {
        int count = 0;
        for (int[] d : DIRS) {
            int nx = pos[0] + d[0];
            int ny = pos[1] + d[1];
            if (inBounds(nx, ny) && !obstacles.contains(key(nx, ny))) {
                count++;
            }
        }
        return count;
    }

    private int heuristic(int[] a, int[] b) {
        return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]); // 曼哈顿距离
    }

    private boolean inBounds(int x, int y) {
        return x >= 0 && x < width && y >= 0 && y < height;
    }

    private String key(int[] pos) {
        return pos[0] + "," + pos[1];
    }

    private String key(int x, int y) {
        return x + "," + y;
    }

    private List<int[]> reconstructPath(Node node) {
        LinkedList<int[]> path = new LinkedList<>();
        while (node != null) {
            path.addFirst(new int[]{node.x, node.y});
            node = node.parent;
        }
        return path;
    }

    // A*节点
    private static class Node {
        int x, y, g, f;
        Node parent;
        Node(int x, int y, int g, int f, Node parent) {
            this.x = x; this.y = y; this.g = g; this.f = f; this.parent = parent;
        }
    }

    // 游戏状态
    public static class GameState {
        public final int[] eagle;
        public final List<int[]> hens;
        public final List<int[]> chicks;
        public final Set<String> obstacles;

        public GameState(int[] eagle, List<int[]> hens,
                        List<int[]> chicks, Set<String> obstacles) {
            this.eagle = eagle;
            this.hens = hens;
            this.chicks = chicks;
            this.obstacles = obstacles;
        }

        public void print() {
            char[][] grid = new char[10][10];
            for (char[] row : grid) Arrays.fill(row, '.');
            for (String obs : obstacles) {
                String[] p = obs.split(",");
                grid[Integer.parseInt(p[1])][Integer.parseInt(p[0])] = '#';
            }
            for (int[] h : hens) grid[h[1]][h[0]] = 'H';
            for (int[] c : chicks) grid[c[1]][c[0]] = 'C';
            grid[eagle[1]][eagle[0]] = 'E';

            System.out.println("  0123456789");
            for (int y = 0; y < 10; y++) {
                System.out.print(y + " ");
                for (int x = 0; x < 10; x++) {
                    System.out.print(grid[y][x]);
                }
                System.out.println();
            }
        }
    }

    public static void main(String[] args) {
        EagleChaseGame game = new EagleChaseGame(10, 10);

        // 初始状态
        int[] eagle = {0, 0};
        List<int[]> hens = Arrays.asList(new int[]{3, 3}, new int[]{6, 4});
        List<int[]> chicks = Arrays.asList(new int[]{8, 8}, new int[]{9, 2}, new int[]{7, 9});
        Set<String> obstacles = new HashSet<>(Arrays.asList("4,4", "4,5", "5,4", "5,5"));

        GameState state = new GameState(eagle, hens, chicks, obstacles);

        System.out.println("===== 老鹰捉小鸡模拟 =====");
        System.out.println("E=老鹰 H=母鸡 C=小鸡 #=障碍物");
        System.out.println("\n初始状态:");
        state.print();

        // 老鹰选择目标
        int[] target = game.selectBestTarget(state.eagle, state.chicks, state.hens, state.obstacles);
        System.out.println("\n老鹰选定目标: (" + target[0] + "," + target[1] + ")");

        // 计算拦截点
        List<int[]> intercepts = game.computeInterceptionPoints(
            state.eagle, target, state.hens, state.obstacles);
        System.out.println("有效拦截点数量: " + intercepts.size());

        // 模拟10回合
        for (int turn = 1; turn <= 10 && !state.chicks.isEmpty(); turn++) {
            state = game.simulateTurn(state);
            System.out.println("\n第" + turn + "回合:");
            state.print();
            System.out.println("剩余小鸡: " + state.chicks.size());
        }
    }
}

算法核心要点

动态代价A*搜索

传统A*使用固定地图,而本实现将母鸡位置转化为动态代价函数:
– 紧邻母鸡:代价+5
– 附近母鸡:代价+2
– 空旷区域:代价+1

这保证了老鹰会主动绕开母鸡的防守区域,同时不会在完全无路可走时陷入死锁。

多智能体协同

三方角色各有策略:
– 老鹰:最大化抓捕效率,动态重选目标
– 母鸡:预判老鹰路径,抢占拦截点
– 小鸡:逃离老鹰同时靠近母鸡寻求庇护

复杂度分析

模块 时间复杂度 空间复杂度
A*寻路 O(b^d),b为分支因子,d为解深度 O(V)
目标选择 O(c × (A*代价)),c为小鸡数量 O(V)
拦截点计算 O(path × h),h为母鸡数量 O(path × h)
单回合模拟 O(c + h)次A*搜索 O(V)

总结

老鹰捉小鸡将多智能体路径规划问题具象化为熟悉的游戏场景。通过A*搜索的动态代价扩展、多目标评分函数和拦截点预判,我们构建了一个能自主决策的三方博弈系统。这种”追逐-拦截-逃逸”的框架广泛适用于机器人足球、军事仿真和无人机编队等实际场景。