java案例统计反越位成功形成单刀几次?

wen java案例 1

本文目录导读:

java案例统计反越位成功形成单刀几次?

  1. 核心判断逻辑(业务规则)
  2. Java 代码实现
  3. 输出示例
  4. 如何扩展成真实系统?

要统计“反越位成功形成单刀”的次数,在足球数据统计中属于高级事件识别,通常需要结合位置数据(x,y坐标)事件数据

由于你提出的是“Java案例”,我假设你已经有比赛事件数据(如传球、跑动、越位判罚)或者模拟数据,需要用Java代码来判断。

下面我提供一个基于规则引擎的Java案例,演示如何从事件流中统计这种特定战术场景。


核心判断逻辑(业务规则)

一次“反越位成功形成单刀”需要同时满足以下条件:

  1. 触发事件:进攻方球员在对方防线身后(越位位置)接球。
  2. 关键前提:在传球瞬间,防守方最后一名球员(除门将)的位置在进攻方接球球员的更靠近底线一侧,如果接球时防守方站位靠前,则不构成越位(即反越位成功)。
  3. 结果事件:接球后,球员直接面对对方门将(没有防守球员阻挡),并形成射门或突破。

Java 代码实现

import java.util.*;
import java.util.stream.*;
/**
 * 反越位成功单刀统计器
 */
public class OffsideTrapAnalyzer {
    // ========== 数据结构 ==========
    /** 场上球员位置(简化模型,只包含x,y坐标) */
    static class Player {
        String name;
        double x;  // 0~105米,x越大越靠近对方球门
        double y;  // 0~68米,y是横向位置
        boolean isAttacker; // 是否进攻方
        Player(String name, double x, double y, boolean isAttacker) {
            this.name = name;
            this.x = x;
            this.y = y;
            this.isAttacker = isAttacker;
        }
    }
    /** 传球事件 */
    static class PassEvent {
        int time;        // 比赛时间(秒)
        Player passer;   // 传球者
        Player receiver; // 接球者
        double passX, passY; // 传球瞬间接球者的位置
        PassEvent(int time, Player passer, Player receiver, double passX, double passY) {
            this.time = time;
            this.passer = passer;
            this.receiver = receiver;
            this.passX = passX;
            this.passY = passY;
        }
    }
    /** 射门事件 */
    static class ShotEvent {
        int time;
        Player shooter;
        double x, y;
        ShotEvent(int time, Player shooter, double x, double y) {
            this.time = time;
            this.shooter = shooter;
            this.x = x;
            this.y = y;
        }
    }
    // ========== 核心统计方法 ==========
    /**
     * 统计反越位成功形成单刀的次数
     * @param passes 传球事件列表(已按时间排序)
     * @param shots 射门事件列表
     * @param defenders 防守方球员(不含门将)
     * @param goalkeeper 对方门将位置
     * @param attackers 进攻方球员(用于计算最后一名防守者)
     * @return 成功形成单刀的次数
     */
    public static int countSuccessfulOffsideTrap(List<PassEvent> passes,
                                                 List<ShotEvent> shots,
                                                 List<Player> defenders,
                                                 Player goalkeeper,
                                                 List<Player> attackers) {
        int count = 0;
        // 对每个传球事件,判断是否构成反越位单刀
        for (PassEvent pass : passes) {
            // 1. 接球者必须是进攻方
            if (!pass.receiver.isAttacker) continue;
            // 2. 计算传球瞬间,防守方最后一名球员(不含门将)的x坐标
            double lastDefenderX = getLastDefenderX(defenders, pass.passX, pass.passY);
            // 3. 判断接球者是否在最后一名防守者身后(即越位位置)
            //    如果接球者x > 最后防守者x,则处于越位位置
            if (pass.receiver.x <= lastDefenderX) {
                continue; // 不处于越位位置,不是反越位
            }
            // 4. 判断是否形成单刀:
            //    a) 接球后,接球者到门将的距离 < 最后防守者到门将的距离(至少在门将面前)
            //    b) 或者在接球后短时间内(如2秒内)有射门
            boolean formedOneOnOne = isOneOnOne(pass, shots, defenders, goalkeeper);
            if (formedOneOnOne) {
                count++;
                System.out.println("[成功反越位单刀] 时间: " + pass.time + "秒, 接球者: " + pass.receiver.name
                        + ", 位置: (" + pass.receiver.x + ", " + pass.receiver.y + ")"
                        + ", 最后防守者: " + getLastDefenderName(defenders, pass.passX, pass.passY));
            }
        }
        return count;
    }
    /** 获取最后一名防守者的x坐标(不含门将)——即防线最深处 */
    private static double getLastDefenderX(List<Player> defenders, double passX, double passY) {
        return defenders.stream()
                .mapToDouble(d -> d.x)
                .max()
                .orElse(0);
    }
    private static String getLastDefenderName(List<Player> defenders, double passX, double passY) {
        return defenders.stream()
                .max(Comparator.comparingDouble(d -> d.x))
                .map(d -> d.name)
                .orElse("未知");
    }
    /** 判断是否形成单刀(简化:直接看是否面对门将且无防守干扰,或接球后2秒内射门) */
    private static boolean isOneOnOne(PassEvent pass, List<ShotEvent> shots,
                                      List<Player> defenders, Player goalkeeper) {
        // 条件A:接球后,接球者与门将的距离 < 最后防守者与门将的距离(意味着防守者被甩在身后)
        double receiverToGK = distance(pass.receiver.x, pass.receiver.y, goalkeeper.x, goalkeeper.y);
        double lastDefX = getLastDefenderX(defenders, pass.passX, pass.passY);
        double lastDefToGK = distance(lastDefX, pass.passY, goalkeeper.x, goalkeeper.y); // 简化
        if (receiverToGK < lastDefToGK) {
            return true;
        }
        // 条件B:在接球后2秒内(或极短时间内)有射门,且射门时防守者未贴身
        // 这里简化:查找接球后3秒内的射门事件
        final double TIME_WINDOW = 3.0; // 秒
        boolean hasShotSoon = shots.stream()
                .anyMatch(shot -> shot.time >= pass.time
                        && shot.time <= pass.time + TIME_WINDOW
                        && shot.shooter == pass.receiver);
        // 且射门时防守者不在射门球员和球门之间
        if (hasShotSoon) {
            // 检查是否有防守者在射门球员与球门之间(简化:看看防守者x是否小于射门者x且距离较近)
            boolean defenderBlocking = defenders.stream()
                    .anyMatch(d -> d.x < pass.receiver.x
                            && d.x > pass.receiver.x - 5 // 距离接球者5米内
                            && Math.abs(d.y - pass.receiver.y) < 3);
            return !defenderBlocking;
        }
        return false;
    }
    private static double distance(double x1, double y1, double x2, double y2) {
        return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
    }
    // ========== 测试主方法 ==========
    public static void main(String[] args) {
        // 模拟一场比赛的事件数据
        // 防守方:4名后卫 + 1门将
        Player def1 = new Player("后卫A", 80, 20, false);
        Player def2 = new Player("后卫B", 78, 45, false);
        Player def3 = new Player("后卫C", 82, 55, false);
        Player def4 = new Player("后卫D", 77, 30, false);
        Player gk = new Player("门将", 100, 34, false);
        List<Player> defenders = Arrays.asList(def1, def2, def3, def4);
        // 进攻方
        Player attacker1 = new Player("前锋甲", 85, 50, true);
        Player attacker2 = new Player("前锋乙", 60, 40, true);
        Player attacker3 = new Player("中场丙", 40, 35, true);
        List<Player> attackers = Arrays.asList(attacker1, attacker2, attacker3);
        // 传球事件
        List<PassEvent> passes = new ArrayList<>();
        // 场景1:有效反越位(前锋甲在最后防线身后接球,且最终形成射门)
        passes.add(new PassEvent(10, attacker3, attacker1, 85, 50)); // 接球时x=85 > 最后防线82
        // 场景2:不是反越位(接球者x小于最后防线)
        passes.add(new PassEvent(20, attacker3, attacker2, 60, 40)); // 接球时x=60 < 82,不是越位位置
        // 射门事件
        List<ShotEvent> shots = new ArrayList<>();
        shots.add(new ShotEvent(12, attacker1, 96, 45)); // 接球后2秒射门
        int result = countSuccessfulOffsideTrap(passes, shots, defenders, gk, attackers);
        System.out.println("\n===== 反越位成功形成单刀次数: " + result + " =====");
    }
}

输出示例

[成功反越位单刀] 时间: 10秒, 接球者: 前锋甲, 位置: (85.0, 50.0), 最后防守者: 后卫C
===== 反越位成功形成单刀次数: 1 =====

步骤 业务逻辑 代码实现
1 筛选接球者为进攻方 pass.receiver.isAttacker
2 确定传球瞬间的最后防守者x坐标(防线最靠球门的人) getLastDefenderX()
3 判断接球者是否在防线身后(越位位置) pass.receiver.x > lastDefenderX
4 判断是否形成单刀(无防守干扰 + 面对门将) isOneOnOne():距离判断 + 近期射门判断

如何扩展成真实系统?

  1. 数据来源:用Opta或StatsBomb的公开数据集,包含每个事件的x,y坐标。
  2. 更精确的越位判断
    • 需要获取传球瞬间所有球员的位置,而不是传球前的最终位置。
    • 越位线是倒数第二名防守球员(包括门将或后卫,取决于谁更靠前)。
  3. 单刀判定
    • 需要计算接球者与门将之间的防守者数量(用射线检测或几何关系)。
    • 定义“单刀”为:接球后防守者数量为0,且距离球门30米内。

如果你有具体的赛事数据(JSON/CSV 格式),我可以帮你更进一步实现一个完整的统计模块。

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