java案例统计交叉跑位造成威胁几次?

wen java案例 3

本文目录导读:

java案例统计交叉跑位造成威胁几次?

  1. 完整Java代码
  2. 运行结果
  3. 核心算法思路
  4. 可扩展方向

我来提供一个Java案例,用于统计足球比赛中交叉跑位造成威胁的次数,这个案例会模拟进攻球员通过交叉跑位摆脱防守、形成威胁(如射门、关键传球)的场景。

完整Java代码

import java.util.*;
import java.util.stream.Collectors;
/**
 * 球员位置
 */
class Position {
    private double x; // 横向坐标 0-100
    private double y; // 纵向坐标 0-100
    public Position(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double getX() { return x; }
    public double getY() { return y; }
    public double distanceTo(Position other) {
        return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
    }
    @Override
    public String toString() {
        return String.format("(%.1f, %.1f)", x, y);
    }
}
/**
 * 球员跑位轨迹点
 */
class TrackPoint {
    private final String playerId;
    private final Position position;
    private final long timestamp; // 毫秒
    public TrackPoint(String playerId, Position position, long timestamp) {
        this.playerId = playerId;
        this.position = position;
        this.timestamp = timestamp;
    }
    public String getPlayerId() { return playerId; }
    public Position getPosition() { return position; }
    public long getTimestamp() { return timestamp; }
}
/**
 * 威胁事件(射门、关键传球等)
 */
class ThreatEvent {
    private final long timestamp;
    private final String playerId;
    private final String type; // SHOT, KEY_PASS, ASSIST
    public ThreatEvent(long timestamp, String playerId, String type) {
        this.timestamp = timestamp;
        this.playerId = playerId;
        this.type = type;
    }
    public long getTimestamp() { return timestamp; }
    public String getPlayerId() { return playerId; }
    public String getType() { return type; }
}
/**
 * 交叉跑位威胁统计分析器
 */
public class CrossRunThreatAnalyzer {
    // 交叉跑位判定参数
    private static final double CROSS_DISTANCE_THRESHOLD = 3.0;   // 两名球员交叉时最近距离(米)
    private static final long CROSS_TIME_WINDOW = 1500;            // 交叉时间窗口(毫秒)
    private static final long THREAT_TIME_WINDOW = 5000;           // 交叉后多久内形成威胁(毫秒)
    /**
     * 统计交叉跑位造成威胁的次数
     */
    public int countCrossRunThreats(List<TrackPoint> trackPoints, List<ThreatEvent> threatEvents) {
        // 1. 按球员分组轨迹
        Map<String, List<TrackPoint>> byPlayer = trackPoints.stream()
                .collect(Collectors.groupingBy(TrackPoint::getPlayerId));
        // 2. 按时间排序
        byPlayer.values().forEach(list ->
                list.sort(Comparator.comparingLong(TrackPoint::getTimestamp)));
        Set<Long> countedThreats = new HashSet<>(); // 去重,避免同一威胁重复计数
        int crossThreatCount = 0;
        // 3. 两两球员检测交叉跑位
        List<String> playerIds = new ArrayList<>(byPlayer.keySet());
        for (int i = 0; i < playerIds.size(); i++) {
            for (int j = i + 1; j < playerIds.size(); j++) {
                String p1 = playerIds.get(i);
                String p2 = playerIds.get(j);
                List<TrackPoint> track1 = byPlayer.get(p1);
                List<TrackPoint> track2 = byPlayer.get(p2);
                List<Long> crossTimes = detectCrossRuns(track1, track2);
                // 4. 对每次交叉,判断其后是否产生威胁
                for (long crossTime : crossTimes) {
                    for (ThreatEvent threat : threatEvents) {
                        long delta = threat.getTimestamp() - crossTime;
                        // 交叉后 0 ~ THREAT_TIME_WINDOW 内产生威胁
                        if (delta >= 0 && delta <= THREAT_TIME_WINDOW) {
                            // 威胁由交叉的两名球员之一发起
                            if ((threat.getPlayerId().equals(p1) || threat.getPlayerId().equals(p2))
                                    && countedThreats.add(threat.getTimestamp() * 1000
                                            + threat.getPlayerId().hashCode())) {
                                crossThreatCount++;
                                System.out.printf("交叉跑位[p1=%s, p2=%s] 于 t=%d ms," +
                                                "在 %d ms 后由 %s 形成威胁(%s)%n",
                                        p1, p2, crossTime, delta,
                                        threat.getPlayerId(), threat.getType());
                            }
                        }
                    }
                }
            }
        }
        return crossThreatCount;
    }
    /**
     * 检测两名球员之间发生的所有交叉跑位时刻
     */
    private List<Long> detectCrossRuns(List<TrackPoint> t1, List<TrackPoint> t2) {
        List<Long> crossTimes = new ArrayList<>();
        int i = 0, j = 0;
        boolean lastWasClose = false;
        while (i < t1.size() && j < t2.size()) {
            TrackPoint a = t1.get(i);
            TrackPoint b = t2.get(j);
            long dt = Math.abs(a.getTimestamp() - b.getTimestamp());
            if (dt > CROSS_TIME_WINDOW) {
                // 时间差太大,推进时间较早的点
                if (a.getTimestamp() < b.getTimestamp()) i++; else j++;
                continue;
            }
            double dist = a.getPosition().distanceTo(b.getPosition());
            boolean isClose = dist <= CROSS_DISTANCE_THRESHOLD;
            // 由远及近再拉开:从"近"状态第一次出现即视为交叉点
            if (isClose && !lastWasClose) {
                long crossTime = Math.max(a.getTimestamp(), b.getTimestamp());
                crossTimes.add(crossTime);
            }
            lastWasClose = isClose;
            if (a.getTimestamp() < b.getTimestamp()) i++; else j++;
        }
        return crossTimes;
    }
    // ================== 测试 ==================
    public static void main(String[] args) {
        List<TrackPoint> tracks = new ArrayList<>();
        // 球员 A7 从左向右跑
        tracks.add(new TrackPoint("A7", new Position(30, 50), 1000));
        tracks.add(new TrackPoint("A7", new Position(40, 50), 1500));
        tracks.add(new TrackPoint("A7", new Position(50, 50), 2000)); // 交叉点
        tracks.add(new TrackPoint("A7", new Position(60, 50), 2500));
        // 球员 B9 从右向左跑
        tracks.add(new TrackPoint("B9", new Position(70, 50), 1000));
        tracks.add(new TrackPoint("B9", new Position(60, 50), 1500));
        tracks.add(new TrackPoint("B9", new Position(50, 50), 2000)); // 交叉点
        tracks.add(new TrackPoint("B9", new Position(40, 50), 2500));
        // 无关球员 C10
        tracks.add(new TrackPoint("C10", new Position(20, 20), 1000));
        tracks.add(new TrackPoint("C10", new Position(25, 25), 2000));
        // 威胁事件:交叉后 3 秒,A7 射门
        List<ThreatEvent> threats = Arrays.asList(
                new ThreatEvent(5000, "A7", "SHOT"),
                new ThreatEvent(9000, "C10", "KEY_PASS") // 无关球员的威胁
        );
        CrossRunThreatAnalyzer analyzer = new CrossRunThreatAnalyzer();
        int count = analyzer.countCrossRunThreats(tracks, threats);
        System.out.println("\n>>> 交叉跑位造成威胁的次数:" + count);
    }
}

运行结果

交叉跑位[p1=A7, p2=B9] 于 t=2000 ms,在 3000 ms 后由 A7 形成威胁(SHOT)
>>> 交叉跑位造成威胁的次数:1

核心算法思路

步骤 说明
数据建模 TrackPoint 记录球员位置+时间;ThreatEvent 记录射门/关键传球等威胁
分组排序 按球员ID分组,按时间戳排序
交叉检测 双指针遍历两条轨迹,在同一时间窗口内,两名球员距离 ≤ 3米判定为"接近",从"远"到"近"的状态跳变即视为一次交叉点
威胁关联 在交叉点之后的 5 秒内,若任一交叉球员产生威胁事件,则计为交叉跑位威胁 +1
去重 Set 避免同一次威胁被多次统计

可扩展方向

  1. 速度方向判断:加入方向向量,判断是否真的"反向交叉"(真正的cross run),而非静态接近。
  2. 防守球员关联:加入防守球员数据,判断交叉是否造成"摆脱防守"。
  3. 威胁权重:射门=1,关键传球=0.8,助攻=1.5,而非简单计数。
  4. xG(预期进球):结合位置计算威胁值,而非布尔判断。
  5. 真实数据接入:可对接 Sportradar、StatsBomb 等 API 的 tracking data。

如果你有真实的追踪数据格式(如 JSON/CSV),我可以帮你把解析部分也补上,需要针对11人全场特定战术场景扩展吗?

抱歉,评论功能暂时关闭!