每日算法 — 使用java实现射箭游戏:抛物线轨迹预测与风力补偿贪心策略

引言

射箭游戏是物理与策略结合的经典游戏类型:玩家拉弓射箭,箭矢在空中受重力影响形成抛物线轨迹,同时还需要考虑风力干扰。本文将用 Java 完整实现射箭游戏的核心算法系统,重点讲解抛物线轨迹预测模型与风力补偿贪心瞄准策略两大技术。

抛物线物理模型

运动学方程

箭矢射出后,在水平方向受初速度 vx 和风力 wind 影响,垂直方向受重力 g 影响。设发射角度为 θ,初速度为 v0,则:

  • 水平初速度:vx = v0 × cos(θ)
  • 垂直初速度:vy = v0 × sin(θ)
  • 水平位移:x(t) = (vx + wind) × t
  • 垂直位移:y(t) = vy × t - 0.5 × g × t²

由 y(t) = 0 可解出飞行时间 t = 2 × vy / g,代入水平位移得射程公式:

射程:R = (v0 × cos(θ) + wind) × 2 × v0 × sin(θ) / g

/**
 * 箭矢物理实体
 */
public class Arrow {
    private static final double GRAVITY = 9.8;   // 重力加速度 m/s²
    private static final double DT = 0.016;       // 模拟时间步长(约60FPS)

    private double x, y;    // 位置(米)
    private double vx, vy;  // 速度分量
    private boolean flying; // 是否飞行中

    public Arrow(double startX, double startY) {
        this.x = startX;
        this.y = startY;
        this.flying = false;
    }

    /**
     * 发射箭矢
     * @param angle 发射角度(弧度)
     * @param power 拉弓力度(m/s)
     * @param wind 水平风力(m/s,正为顺风)
     */
    public void shoot(double angle, double power, double wind) {
        this.vx = power * Math.cos(angle) + wind;
        this.vy = power * Math.sin(angle);
        this.flying = true;
    }

    /**
     * 物理更新
     */
    public void update() {
        if (!flying) return;

        x += vx * DT;
        y += vy * DT;
        vy -= GRAVITY * DT; // 重力减速

        if (y <= 0) {
            y = 0;
            flying = false; // 落地
        }
    }

    /**
     * 预测落点:不模拟直接计算解析解
     */
    public static double predictLanding(double startX, double startY,
                                         double angle, double power, double wind) {
        double vy0 = power * Math.sin(angle);
        double vx0 = power * Math.cos(angle) + wind;

        // 飞行时间:解方程 y0 + vy*t - 0.5*g*t² = 0
        double discriminant = vy0 * vy0 + 2 * GRAVITY * startY;
        if (discriminant < 0) return startX; // 不会落地(理论上不会发生)

        double t = (vy0 + Math.sqrt(discriminant)) / GRAVITY;
        return startX + vx0 * t;
    }

    // Getters...
    public double getX() { return x; }
    public double getY() { return y; }
    public boolean isFlying() { return flying; }
}

风力补偿与瞄准策略

贪心最优角度计算

给定目标距离 targetX 和当前风力 wind,需要找到发射角度 θ 使得箭矢恰好命中目标。这是一个单变量非线性方程求根问题,可用二分搜索或牛顿迭代法求解。

/**
 * 射箭 AI:贪心策略计算最优发射参数
 */
public class ArcheryAI {

    private static final double GRAVITY = 9.8;
    private static final double MAX_POWER = 80; // 最大拉弓力度

    /**
     * 二分搜索求解最佳发射角度
     * @param startX 射手X坐标
     * @param startY 射手Y坐标
     * @param targetX 目标X坐标
     * @param power 固定拉弓力度
     * @param wind 风力
     * @return 最佳发射角度(弧度),无解返回NaN
     */
    public static double findOptimalAngle(double startX, double startY,
                                           double targetX, double power, double wind) {
        double distance = targetX - startX;
        double low = 0.01;      // 略高于0度
        double high = Math.PI / 2 - 0.01; // 略低于90度

        // 检查是否有解:最大射程是否能到达目标
        double maxRange = Arrow.predictLanding(startX, startY, high, power, wind);
        if (maxRange < distance) return Double.NaN;

        // 二分搜索使落点尽可能接近目标
        for (int i = 0; i < 50; i++) {
            double mid = (low + high) / 2;
            double landing = Arrow.predictLanding(startX, startY, mid, power, wind);

            if (landing < distance) {
                low = mid; // 角度太小,射程不够,增大角度
            } else {
                high = mid; // 角度太大,射程过远,减小角度
            }
        }
        return (low + high) / 2;
    }

    /**
     * 动态调整力度与角度的贪心策略
     * 优先使用较小力度(更精准),力度不足时增大
     */
    public static ShotParams computeGreedyShot(double startX, double startY,
                                                double targetX, double wind) {
        double bestAngle = Double.NaN;
        double bestPower = MAX_POWER;
        double minError = Double.MAX_VALUE;

        // 贪心搜索:在力度和角度空间中寻找最优组合
        for (double power = 30; power <= MAX_POWER; power += 2) {
            double angle = findOptimalAngle(startX, startY, targetX, power, wind);
            if (Double.isNaN(angle)) continue;

            double landing = Arrow.predictLanding(startX, startY, angle, power, wind);
            double error = Math.abs(landing - targetX);

            // 贪心:优先选择误差小且力度小的方案
            if (error < minError || (Math.abs(error - minError) < 0.1 && power < bestPower)) {
                minError = error;
                bestAngle = angle;
                bestPower = power;
            }
        }

        return new ShotParams(bestAngle, bestPower, minError);
    }

    /**
     * 射击参数封装
     */
    public static class ShotParams {
        public final double angle;   // 弧度
        public final double power;   // 力度
        public final double error;   // 预期误差(米)

        public ShotParams(double angle, double power, double error) {
            this.angle = angle;
            this.power = power;
            this.error = error;
        }

        @Override
        public String toString() {
            return String.format("角度=%.2f°, 力度=%.1f, 预期误差=%.3fm",
                Math.toDegrees(angle), power, error);
        }
    }
}

完整游戏系统

import java.util.*;

/**
 * 射箭游戏完整实现
 */
public class ArcheryGame {

    private double wind;           // 当前风力(随机变化)
    private double targetX;        // 靶子位置
    private double targetRadius;   // 靶子半径
    private int score;
    private Random random = new Random();

    public ArcheryGame() {
        this.targetRadius = 0.5; // 靶心半径0.5米
        nextRound();
    }

    /**
     * 生成新一轮目标
     */
    public void nextRound() {
        this.wind = (random.nextDouble() - 0.5) * 10; // -5 ~ +5 m/s
        this.targetX = 30 + random.nextDouble() * 50; // 30-80米
        System.out.printf("%n新一轮!目标距离: %.1f米, 风力: %.2f m/s%n", targetX, wind);
    }

    /**
     * AI自动射击
     */
    public void aiShoot() {
        ArcheryAI.ShotParams params = ArcheryAI.computeGreedyShot(0, 1.5, targetX, wind);

        if (Double.isNaN(params.angle)) {
            System.out.println("目标超出最大射程,本轮跳过");
            return;
        }

        System.out.println("AI决策: " + params);

        // 模拟箭矢飞行
        Arrow arrow = new Arrow(0, 1.5);
        arrow.shoot(params.angle, params.power, wind);

        // 逐步模拟直到落地
        while (arrow.isFlying()) {
            arrow.update();
        }

        // 计算得分
        double distanceFromCenter = Math.abs(arrow.getX() - targetX);
        int points = calculateScore(distanceFromCenter);
        score += points;

        System.out.printf("落点: %.2f米, 偏离靶心: %.2f米, 得分: %d, 总分: %d%n",
            arrow.getX(), distanceFromCenter, points, score);
    }

    private int calculateScore(double deviation) {
        if (deviation <= 0.1) return 10; // 十环
        if (deviation <= 0.25) return 9;
        if (deviation <= 0.4) return 8;
        if (deviation <= targetRadius) return 7;
        if (deviation <= targetRadius * 2) return 5;
        return 0;
    }

    /**
     * 玩家手动射击(简化版)
     */
    public void playerShoot(double angleDeg, double power) {
        double angle = Math.toRadians(angleDeg);
        Arrow arrow = new Arrow(0, 1.5);
        arrow.shoot(angle, power, wind);

        while (arrow.isFlying()) arrow.update();

        double deviation = Math.abs(arrow.getX() - targetX);
        int points = calculateScore(deviation);
        score += points;

        System.out.printf("落点: %.2f米, 偏离: %.2f米, 得分: %d%n",
            arrow.getX(), deviation, points);
    }

    public static void main(String[] args) {
        ArcheryGame game = new ArcheryGame();

        // AI演示10轮
        for (int i = 0; i < 10; i++) {
            game.aiShoot();
            game.nextRound();
        }

        System.out.printf("%n最终得分: %d%n", game.score);
    }
}

进阶:多目标追踪与动态修正

/**
 * 移动靶追踪:考虑目标速度的提前量计算
 */
public class MovingTargetTracker {

    /**
     * 计算瞄准提前点
     * @param targetX 目标当前位置
     * @param targetVx 目标水平速度
     * @param arrowSpeed 箭速
     * @return 应瞄准的位置
     */
    public static double computeLeadPoint(double targetX, double targetVx, 
                                           double arrowSpeed, double distance) {
        // 估算箭矢飞行时间
        double estimatedTime = distance / arrowSpeed;
        // 目标在飞行时间内的位移
        return targetX + targetVx * estimatedTime;
    }

    /**
     * 迭代修正:考虑箭矢飞行过程中目标持续移动
     */
    public static double iterativeLead(double shooterX, double shooterY,
                                        double targetX, double targetY,
                                        double targetVx, double targetVy,
                                        double arrowSpeed) {
        double aimX = targetX;
        double aimY = targetY;

        for (int i = 0; i < 5; i++) {
            double dist = Math.hypot(aimX - shooterX, aimY - shooterY);
            double flightTime = dist / arrowSpeed;

            aimX = targetX + targetVx * flightTime;
            aimY = targetY + targetVy * flightTime;
        }
        return aimX;
    }
}

复杂度分析

模块 时间复杂度 说明
落点预测 O(1) 解析公式直接计算
角度二分搜索 O(log precision) 约50次迭代达到双精度
贪心力度搜索 O((MAX-30)/2 × log precision) 遍历力度空间
迭代修正 O(iterations) 通常3-5次收敛

总结

射箭游戏算法将经典力学与数值优化完美融合:解析公式实现 O(1) 落点预测,二分搜索在微秒级求解最优角度,贪心策略在力度-精度间取得平衡。这种”解析+数值”的混合方法在弹道计算、机器人投掷等工程领域有广泛应用。