篮球是一项充满策略与协作的团队运动。本文用Java实现篮球比赛的核心算法模拟,重点讲解团队传球网络的图论建模、球员跑位路径规划、防守覆盖计算以及基于期望值的贪心最优投篮决策算法。
一、核心算法模块
篮球战术模拟可拆解为以下算法问题:
- 传球网络:将球员视为节点,传球路线视为边,构建动态图
- 跑位路径:使用A*或Dijkstra计算球员到目标位置的最优路径
- 防守覆盖:计算防守球员对进攻球员的覆盖面积与干扰概率
- 投篮决策:综合投篮命中率、时间、比分差等因素计算最优选择
二、投篮命中率模型
投篮命中率受多种因素影响,可建模为:
$$P_{make} = P_{base} \times D_{defense} \times D_{fatigue} \times D_{distance}$$
其中:
– $P_{base}$:球员基础命中率
– $D_{defense}$:防守干扰因子(被紧逼时下降)
– $D_{fatigue}$:体力衰减因子
– $D_{distance}$:距离衰减因子(通常呈指数衰减)
三、Java完整实现
import java.util.*;
/**
* 篮球战术模拟核心算法
* 包含传球网络、跑位路径、防守覆盖与最优投篮决策
*/
public class BasketballSimulation {
// 球场尺寸(标准NBA半场)
private static final double COURT_WIDTH = 15.0; // m
private static final double COURT_HEIGHT = 14.0; // m
private static final double BASKET_X = 7.5; // 篮筐X
private static final double BASKET_Y = 0.0; // 篮筐Y(底线)
private static final double THREE_POINT_RADIUS = 7.24; // 三分线半径
private static final double DT = 0.1; // 模拟时间步长(秒)
private List<Player> teamA = new ArrayList<>(); // 进攻方
private List<Player> teamB = new ArrayList<>(); // 防守方
private Ball ball;
private double gameClock = 24.0; // 进攻时间
private int scoreA = 0, scoreB = 0;
/**
* 二维向量工具类
*/
static class Vec2 {
double x, y;
Vec2(double x, double y) { this.x = x; this.y = y; }
Vec2 add(Vec2 v) { return new Vec2(x + v.x, y + v.y); }
Vec2 sub(Vec2 v) { return new Vec2(x - v.x, y - v.y); }
Vec2 mul(double s) { return new Vec2(x * s, y * s); }
double len() { return Math.sqrt(x * x + y * y); }
double dist(Vec2 v) { return sub(v).len(); }
Vec2 norm() {
double l = len();
return l > 0 ? new Vec2(x / l, y / l) : new Vec2(0, 0);
}
@Override
public String toString() { return String.format("(%.1f, %.1f)", x, y); }
}
/**
* 球员实体
*/
static class Player {
String name;
Vec2 pos; // 位置
Vec2 vel; // 速度
double speed; // 最大移动速度 m/s
double baseFG; // 基础投篮命中率
double threeFG; // 三分命中率
double stamina; // 体力 0-100
double height; // 身高 m
boolean hasBall; // 是否持球
Vec2 targetPos; // 目标跑位位置
Player(String name, double x, double y, double speed,
double fg, double three, double height) {
this.name = name;
this.pos = new Vec2(x, y);
this.vel = new Vec2(0, 0);
this.speed = speed;
this.baseFG = fg;
this.threeFG = three;
this.stamina = 100;
this.height = height;
this.hasBall = false;
this.targetPos = new Vec2(x, y);
}
/**
* 向目标位置移动
*/
void moveTowards(Vec2 target, double dt) {
Vec2 dir = target.sub(pos);
double dist = dir.len();
if (dist < 0.1) {
vel = new Vec2(0, 0);
return;
}
// 速度与体力相关
double currentSpeed = speed * (0.5 + stamina / 200.0);
double moveDist = Math.min(dist, currentSpeed * dt);
vel = dir.norm().mul(moveDist / dt);
pos = pos.add(dir.norm().mul(moveDist));
// 移动消耗体力
stamina -= moveDist * 0.5;
stamina = Math.max(0, stamina);
}
/**
* 计算当前位置的投篮命中率
*/
double calculateShotProbability() {
double distToBasket = pos.dist(new Vec2(BASKET_X, BASKET_Y));
// 基础命中率选择(两分/三分)
double base = distToBasket > THREE_POINT_RADIUS ? threeFG : baseFG;
// 距离衰减:每远离1米命中率下降约5%
double distFactor = Math.exp(-distToBasket * 0.08);
// 体力衰减
double fatigueFactor = 0.5 + stamina / 200.0;
return base * distFactor * fatigueFactor;
}
}
/**
* 篮球实体
*/
static class Ball {
Vec2 pos;
Player holder; // 持球者
Vec2 targetPos; // 传球目标位置
double passProgress; // 传球进度 0-1
boolean inAir; // 是否在空中
Ball() {
this.pos = new Vec2(7.5, 7.0);
this.holder = null;
this.inAir = false;
}
}
BasketballSimulation() {
// 初始化进攻方(5人)
teamA.add(new Player("控卫", 7.5, 10.0, 5.0, 0.45, 0.38, 1.90));
teamA.add(new Player("分卫", 11.0, 9.0, 4.8, 0.44, 0.40, 1.95));
teamA.add(new Player("小前锋", 4.0, 9.0, 5.2, 0.47, 0.35, 2.00));
teamA.add(new Player("大前锋", 10.0, 5.0, 4.5, 0.50, 0.30, 2.05));
teamA.add(new Player("中锋", 5.0, 5.0, 4.0, 0.55, 0.15, 2.10));
// 初始化防守方
teamB.add(new Player("防控卫", 7.5, 8.5, 5.0, 0.30, 0.25, 1.88));
teamB.add(new Player("防分卫", 10.5, 8.0, 4.8, 0.30, 0.25, 1.93));
teamB.add(new Player("防小前", 4.5, 8.0, 5.2, 0.30, 0.25, 1.98));
teamB.add(new Player("防大前", 9.5, 4.0, 4.5, 0.30, 0.25, 2.03));
teamB.add(new Player("防中锋", 5.5, 4.0, 4.0, 0.30, 0.25, 2.08));
this.ball = new Ball();
teamA.get(0).hasBall = true; // 控卫持球
ball.holder = teamA.get(0);
}
/**
* 计算防守覆盖因子:防守者距离越近,干扰越大
*/
double calculateDefenseFactor(Player shooter) {
double factor = 1.0;
for (Player defender : teamB) {
double dist = shooter.pos.dist(defender.pos);
// 紧逼防守(<1m):命中率下降40%
// 一般防守(1-3m):命中率下降10-40%
// 放空(>3m):无影响
if (dist < 1.0) {
factor *= 0.6;
} else if (dist < 3.0) {
factor *= (0.6 + 0.4 * (dist - 1.0) / 2.0);
}
}
return Math.max(0.3, factor);
}
/**
* 寻找最优传球目标
* 评估指标:接球后投篮期望得分 + 进攻空间开阔度
*/
Player findBestPassTarget(Player passer) {
Player best = null;
double bestScore = Double.NEGATIVE_INFINITY;
for (Player teammate : teamA) {
if (teammate == passer) continue;
// 传球可行性:路径上是否有防守拦截风险
double passRisk = calculatePassRisk(passer.pos, teammate.pos);
if (passRisk > 0.7) continue; // 风险过高不传球
// 接球后投篮期望值
double shotProb = teammate.calculateShotProbability();
double defenseFactor = calculateDefenseFactor(teammate);
double isThree = teammate.pos.dist(new Vec2(BASKET_X, BASKET_Y)) > THREE_POINT_RADIUS ? 1 : 0;
double expectedPoints = shotProb * defenseFactor * (2 + isThree);
// 综合得分:期望值 - 传球风险惩罚
double score = expectedPoints - passRisk * 2;
if (score > bestScore) {
bestScore = score;
best = teammate;
}
}
return best;
}
/**
* 计算传球风险:基于防守球员与传球线的距离
*/
double calculatePassRisk(Vec2 from, Vec2 to) {
double maxRisk = 0;
for (Player defender : teamB) {
double dist = pointToLineDistance(defender.pos, from, to);
// 距离传球线越近,拦截风险越高
double risk = Math.max(0, 1.0 - dist / 2.0);
maxRisk = Math.max(maxRisk, risk);
}
return maxRisk;
}
/**
* 点到线段距离
*/
double pointToLineDistance(Vec2 p, Vec2 a, Vec2 b) {
Vec2 ab = b.sub(a);
Vec2 ap = p.sub(a);
double t = Math.max(0, Math.min(1, (ap.x * ab.x + ap.y * ab.y) / (ab.x * ab.x + ab.y * ab.y)));
Vec2 closest = a.add(ab.mul(t));
return p.dist(closest);
}
/**
* 执行投篮
*/
boolean shoot(Player shooter) {
double prob = shooter.calculateShotProbability();
prob *= calculateDefenseFactor(shooter);
boolean made = Math.random() < prob;
double dist = shooter.pos.dist(new Vec2(BASKET_X, BASKET_Y));
int points = dist > THREE_POINT_RADIUS ? 3 : 2;
if (made) {
scoreA += points;
System.out.printf("🏀 %s %.1fm处命中%d分球!命中率%.1f%%%n",
shooter.name, dist, points, prob * 100);
} else {
System.out.printf("❌ %s %.1fm处投篮不中 (预期%.1f%%)%n",
shooter.name, dist, prob * 100);
}
shooter.hasBall = false;
ball.holder = null;
gameClock = 24.0; // 重置进攻时间
return made;
}
/**
* 执行传球
*/
void pass(Player from, Player to) {
from.hasBall = false;
to.hasBall = true;
ball.holder = to;
System.out.printf("🔄 %s → %s%n", from.name, to.name);
}
/**
* 更新防守球员位置:紧贴对位进攻球员
*/
void updateDefense() {
for (int i = 0; i < teamB.size() && i < teamA.size(); i++) {
Player defender = teamB.get(i);
Player target = teamA.get(i);
// 人盯人防守:保持在进攻者与篮筐之间
Vec2 basket = new Vec2(BASKET_X, BASKET_Y);
Vec2 dir = target.pos.sub(basket).norm();
Vec2 idealPos = target.pos.sub(dir.mul(1.2)); // 距离1.2m
defender.moveTowards(idealPos, DT);
}
}
/**
* 更新进攻跑位
*/
void updateOffense() {
// 持球者寻找最优动作
Player ballHandler = ball.holder;
if (ballHandler == null) return;
// 决策1:是否直接投篮?
double shotProb = ballHandler.calculateShotProbability();
shotProb *= calculateDefenseFactor(ballHandler);
double expectedValue = shotProb * 2; // 简化:按2分计算
// 决策2:传球给队友的期望
Player passTarget = findBestPassTarget(ballHandler);
double passExpected = 0;
if (passTarget != null) {
double targetProb = passTarget.calculateShotProbability();
targetProb *= calculateDefenseFactor(passTarget);
passExpected = targetProb * 2;
}
// 贪心决策:选择期望得分更高的动作
if (expectedValue >= passExpected && expectedValue > 0.3) {
shoot(ballHandler);
} else if (passTarget != null) {
pass(ballHandler, passTarget);
} else {
// 没有好机会,运球寻找空间
Vec2 basket = new Vec2(BASKET_X, BASKET_Y);
Vec2 driveDir = basket.sub(ballHandler.pos).norm();
ballHandler.moveTowards(ballHandler.pos.add(driveDir.mul(2)), DT);
}
// 无球跑位:其他球员寻找空位
for (Player p : teamA) {
if (p.hasBall) continue;
// 简单跑位:向三分线外拉开空间
Vec2 basket = new Vec2(BASKET_X, BASKET_Y);
double dist = p.pos.dist(basket);
if (dist < THREE_POINT_RADIUS * 0.8) {
Vec2 away = p.pos.sub(basket).norm();
p.targetPos = p.pos.add(away.mul(3));
}
p.moveTowards(p.targetPos, DT);
}
}
/**
* 模拟一次进攻回合
*/
void simulatePossession() {
System.out.println("\n=== 进攻回合开始 ===");
gameClock = 24.0;
// 重置球权到控卫
for (Player p : teamA) p.hasBall = false;
teamA.get(0).hasBall = true;
ball.holder = teamA.get(0);
while (gameClock > 0 && ball.holder != null) {
updateDefense();
updateOffense();
gameClock -= DT;
// 体力自然恢复
for (Player p : teamA) { p.stamina = Math.min(100, p.stamina + 0.05); }
for (Player p : teamB) { p.stamina = Math.min(100, p.stamina + 0.05); }
}
System.out.printf("比分 A:%d - B:%d%n", scoreA, scoreB);
}
public static void main(String[] args) {
BasketballSimulation sim = new BasketballSimulation();
// 模拟5次进攻回合
for (int i = 0; i < 5; i++) {
sim.simulatePossession();
}
System.out.println("\n=== 最终比分 ===");
System.out.printf("A队 %d : %d B队%n", sim.scoreA, sim.scoreB);
}
}
四、传球网络的图论分析
将球队传球建模为有向图 $G=(V, E)$,其中顶点 $V$ 为球员,边 $E$ 为传球。引入网络流概念分析进攻效率:
- 节点容量:球员的持球处理能力
- 边权重:传球成功率 × 接球后得分期望
- 最大流:从持球者到篮筐的最优进攻路径
使用Edmonds-Karp或Dinic算法求解最大流,可量化评估战术路线的有效性。
五、投篮决策的期望最大化
将投篮决策建模为马尔可夫决策过程(MDP):
- 状态:球员位置、剩余时间、比分差、体力
- 动作:投篮、传球、运球突破
- 奖励:得分、时间消耗、防守吸引
通过值迭代或蒙特卡洛树搜索求解最优策略,但贪心策略在实时性要求高的场景中已能取得不错效果。
六、总结
本文通过Java实现了篮球战术模拟的核心算法。从传球网络到投篮决策,每个模块都展示了算法在体育模拟中的应用。核心收获包括:
- 投篮命中率的距离-体力-防守多因子模型
- 传球风险评估的几何计算方法(点到线段距离)
- 贪心决策框架:比较各动作的期望得分选择最优
- 人盯人防守的位置计算(保持在进攻者与篮筐之间)