每日算法 — 使用java实现飞镖游戏:抛物线轨迹拟合与蒙特卡洛最优瞄准策略

游戏场景与算法概述

飞镖是一项考验精准度的投掷运动。玩家站在固定距离外,将飞镖投向靶盘,不同区域对应不同得分。本文用Java实现两个核心算法:抛物线轨迹物理模拟(考虑重力与初始速度)和蒙特卡洛最优瞄准策略(在随机抖动条件下寻找期望得分最高的瞄准点)。

核心算法一:抛物线轨迹模拟

飞镖在空中受重力影响做抛物线运动。给定瞄准角度和初速度,计算飞镖落点坐标。

public class TrajectorySimulator {
    private static final double GRAVITY = 9.81; // m/s²
    private static final double DISTANCE = 2.37; // 标准投镖距离(米)
    private static final double DART_MASS = 0.025; // 飞镖质量(kg)

    // 根据发射角度和初速度计算落点(靶盘平面上的y,z坐标)
    public static HitPoint simulate(double angleY, double angleZ, double velocity) {
        double vy = velocity * Math.cos(angleY) * Math.sin(angleZ);
        double vz = velocity * Math.sin(angleY);
        double vx = velocity * Math.cos(angleY) * Math.cos(angleZ);

        // 计算到达靶盘所需时间
        double t = DISTANCE / vx;

        // 落点坐标(靶盘中心为原点)
        double y = vy * t; // 水平偏移
        double z = vz * t - 0.5 * GRAVITY * t * t; // 垂直偏移(受重力影响)

        return new HitPoint(y, z, t);
    }

    // 根据目标落点反推需要的角度和速度
    public static AimParams solveForTarget(double targetY, double targetZ, double velocity) {
        double t = DISTANCE / velocity;
        double angleZ = Math.atan2(targetY, DISTANCE);
        double vz = (targetZ + 0.5 * GRAVITY * t * t) / t;
        double angleY = Math.asin(vz / velocity);
        return new AimParams(angleY, angleZ, velocity);
    }

    public static class HitPoint {
        public double y, z, time;
        public HitPoint(double y, double z, double t) { this.y = y; this.z = z; this.time = t; }
        public double distanceFromCenter() { return Math.hypot(y, z); }
    }

    public static class AimParams {
        public double angleY, angleZ, velocity;
        public AimParams(double ay, double az, double v) { angleY = ay; angleZ = az; velocity = v; }
    }
}

核心算法二:靶盘得分计算

标准飞镖靶由内到外分为:双倍牛眼(50)、单倍牛眼(25)、三重环(3×区号)、单倍内环(1×区号)、双倍环(2×区号)、单倍外环(1×区号)。

public class DartboardScorer {
    // 标准靶盘区号分布(从顶部12点方向顺时针)
    private static final int[] SECTORS = {20,1,18,4,13,6,10,15,2,17,3,19,7,16,8,11,14,9,12,5};
    private static final double BULL_RADIUS = 6.35; // mm
    private static final double OUTER_BULL_RADIUS = 15.9;
    private static final double INNER_TRIPLE_RADIUS = 99.0;
    private static final double OUTER_TRIPLE_RADIUS = 107.0;
    private static final double INNER_DOUBLE_RADIUS = 162.0;
    private static final double OUTER_DOUBLE_RADIUS = 170.0;

    public static int calculateScore(double y, double z) {
        double r = Math.hypot(y, z);
        double angle = Math.toDegrees(Math.atan2(z, y));
        if (angle < 0) angle += 360;

        // 牛眼区域
        if (r <= BULL_RADIUS) return 50;
        if (r <= OUTER_BULL_RADIUS) return 25;
        if (r > OUTER_DOUBLE_RADIUS) return 0; // 脱靶

        // 确定区号
        int sectorIndex = (int)((angle + 9) / 18) % 20;
        int baseScore = SECTORS[sectorIndex];

        // 确定倍率
        if (r >= INNER_TRIPLE_RADIUS && r <= OUTER_TRIPLE_RADIUS) return baseScore * 3;
        if (r >= INNER_DOUBLE_RADIUS && r <= OUTER_DOUBLE_RADIUS) return baseScore * 2;
        return baseScore; // 单倍区
    }
}

核心算法三:蒙特卡洛最优瞄准

实际投掷时,玩家手部会有随机抖动。我们用蒙特卡洛模拟评估不同瞄准点的期望得分,找到最优瞄准位置。

import java.util.Random;

public class MonteCarloAimer {
    private Random random = new Random();
    private double jitterY; // 水平方向标准差(mm)
    private double jitterZ; // 垂直方向标准差(mm)

    public MonteCarloAimer(double jitterY, double jitterZ) {
        this.jitterY = jitterY; this.jitterZ = jitterZ;
    }

    // 评估某个瞄准点的期望得分
    public double evaluateAimPoint(double aimY, double aimZ, int simulations) {
        int totalScore = 0;
        for (int i = 0; i < simulations; i++) {
            double actualY = aimY + random.nextGaussian() * jitterY;
            double actualZ = aimZ + random.nextGaussian() * jitterZ;
            totalScore += DartboardScorer.calculateScore(actualY, actualZ);
        }
        return (double) totalScore / simulations;
    }

    // 在靶盘范围内搜索最优瞄准点(网格搜索 + 局部细化)
    public AimPoint findOptimalAim(int simulations) {
        double bestScore = -1;
        double bestY = 0, bestZ = 0;

        // 第一阶段:粗网格搜索
        for (double y = -150; y <= 150; y += 10) {
            for (double z = -150; z <= 150; z += 10) {
                double score = evaluateAimPoint(y, z, simulations / 10);
                if (score > bestScore) {
                    bestScore = score; bestY = y; bestZ = z;
                }
            }
        }

        // 第二阶段:在最优点附近细化搜索
        for (double y = bestY - 10; y <= bestY + 10; y += 2) {
            for (double z = bestZ - 10; z <= bestZ + 10; z += 2) {
                double score = evaluateAimPoint(y, z, simulations);
                if (score > bestScore) {
                    bestScore = score; bestY = y; bestZ = z;
                }
            }
        }

        return new AimPoint(bestY, bestZ, bestScore);
    }

    public static class AimPoint {
        public double y, z, expectedScore;
        public AimPoint(double y, double z, double score) {
            this.y = y; this.z = z; this.expectedScore = score;
        }
    }

    public static void main(String[] args) {
        MonteCarloAimer aimer = new MonteCarloAimer(15, 15); // 15mm抖动
        AimPoint best = aimer.findOptimalAim(50000);
        System.out.printf("Optimal aim: (%.1f, %.1f), Expected score: %.2f%n",
            best.y, best.z, best.expectedScore);
    }
}

复杂度分析

算法模块 时间复杂度 空间复杂度
抛物线轨迹计算 O(1) O(1)
靶盘得分判定 O(1) O(1)
单点蒙特卡洛评估 O(s),s为模拟次数 O(1)
网格搜索最优瞄准 O((W/Δ)² × s),W为搜索范围 O(1)

总结

本文通过抛物线物理模型精确模拟飞镖轨迹,利用蒙特卡洛方法在随机抖动条件下寻找最优瞄准策略。读者可以扩展风速补偿、不同飞镖重量等参数,深入理解物理模拟与概率优化在实际问题中的应用。