Files
nba-fan-hub/playoffs.py
T

145 lines
6.0 KiB
Python

# -*- coding: utf-8 -*-
"""
季后赛对阵图生成器
==================
从 games 表按赛季提取季后赛比赛,按系列分组统计胜负,
结合 standings 种子排名,输出标准 bracket 结构(东部/西部/总决赛)。
系列规则:同轮次内共享同一对球队的比赛归为一个系列;胜者 = 胜场多者(平局取最后一场胜者)。
"""
import re
from collections import OrderedDict
from db import query
ROUND_ORDER = ["季后赛首轮", "季后赛次轮", "东部决赛", "西部决赛", "总决赛"]
ROUND_CN = {"季后赛首轮": "首轮", "季后赛次轮": "半决赛", "东部决赛": "东部决赛", "西部决赛": "西部决赛", "总决赛": "总决赛"}
ROUND_STEP = {"季后赛首轮": 0, "季后赛次轮": 1, "东部决赛": 2, "西部决赛": 2, "总决赛": 3}
ROUND_INDEX = {r: i for i, r in enumerate(ROUND_ORDER)}
def _load_teams():
return {r["id"]: r for r in query("SELECT id, name, code FROM teams")}
def _load_standings(season):
rows = query("""SELECT s.*, t.name, t.code FROM standings s JOIN teams t ON s.team_id=t.id
WHERE s.season=? ORDER BY s.conference DESC, s.rank""", (season,))
by_conf = {"东部": [], "西部": []}
for r in rows:
if r["conference"] in by_conf:
by_conf[r["conference"]].append(r)
return by_conf
def _series_key(home_id, away_id):
return tuple(sorted((home_id, away_id)))
def _collect_series(games):
"""按 (轮次, 球队对) 分组:返回 {(round_name, pair): [(game, home_team, away_team), ...]}"""
series = OrderedDict()
for g in games:
key = (g["round_name"], _series_key(g["home_team_id"], g["away_team_id"]))
series.setdefault(key, []).append(g)
return series
def _match_result(match_games, teams):
"""统计一个系列:返回 {home:{team,team_id,wins,advance}, away:{...}, games:[game_ids]}
主客方向以第一场为准;每场胜者按实际主客归属计数(主客互换不影响胜场归属)。"""
g0 = match_games[0]
h_id, a_id = g0["home_team_id"], g0["away_team_id"]
h_wins = a_wins = 0
last_winner = None
gids = []
for g in match_games:
gids.append(g["id"])
if g["status"] != "finished":
continue
if g["home_score"] is None or g["away_score"] is None:
continue
winner = g["home_team_id"] if g["home_score"] > g["away_score"] else g["away_team_id"]
last_winner = winner
if winner == h_id:
h_wins += 1
else:
a_wins += 1
if h_wins == a_wins:
h_wins, a_wins = (1, 0) if last_winner == h_id else (0, 1)
h_team, a_team = teams[h_id], teams[a_id]
return {
"home": {"team": h_team["name"], "team_id": h_id, "code": h_team["code"],
"wins": h_wins, "advance": h_wins > a_wins},
"away": {"team": a_team["name"], "team_id": a_id, "code": a_team["code"],
"wins": a_wins, "advance": a_wins > h_wins},
"games": gids,
}
def _winner(m):
return m["home"] if m["home"]["advance"] else m["away"]
def build_bracket(season):
"""主入口:返回该赛季对阵图结构
{
season, rounds: {first: {east:[m..], west:[m..]}, semi: {...}, conf: {east:m, west:m}, final: m}
}
"""
teams = _load_teams()
standings = _load_standings(season)
games = query("""SELECT * FROM games WHERE season=? AND (round_name LIKE '季后赛%'
OR round_name IN ('东部决赛','西部决赛','总决赛'))""", (season,))
series = _collect_series(games)
def find_match(round_name, home_id, away_id):
key = (round_name, _series_key(home_id, away_id))
if key in series:
return _match_result(series[key], teams)
# 无数据系列:按种子高者晋级(占位)
return {
"home": {"team": teams[home_id]["name"], "team_id": home_id, "code": teams[home_id]["code"],
"wins": 0, "advance": True},
"away": {"team": teams[away_id]["name"], "team_id": away_id, "code": teams[away_id]["code"],
"wins": 0, "advance": False},
"games": [], "placeholder": True,
}
out = {"season": season, "rounds": {}}
for conf in ("东部", "西部"):
ranked = standings.get(conf, [])
first, semi = [], []
if len(ranked) >= 8:
pairs = [(ranked[0], ranked[7]), (ranked[3], ranked[4]), # 1v8, 4v5(上半区)
(ranked[1], ranked[6]), (ranked[2], ranked[5])] # 2v7, 3v6(下半区)
first = [find_match("季后赛首轮", p[0]["team_id"], p[1]["team_id"]) for p in pairs]
# 次轮:上半区胜者 vs 下半区胜者(1/8胜 vs 4/5胜,2/7胜 vs 3/6胜)
w1, w2 = _winner(first[0]), _winner(first[1])
w3, w4 = _winner(first[2]), _winner(first[3])
semi = [find_match("季后赛次轮", w1["team_id"], w2["team_id"]),
find_match("季后赛次轮", w3["team_id"], w4["team_id"])]
out["rounds"]["first"] = out["rounds"].get("first", {})
out["rounds"]["semi"] = out["rounds"].get("semi", {})
out["rounds"]["first"][conf] = first
out["rounds"]["semi"][conf] = semi
# 分区决赛
if len(semi) >= 2:
c1, c2 = _winner(semi[0]), _winner(semi[1])
out["rounds"].setdefault("conf", {})[conf] = find_match(
"东部决赛" if conf == "东部" else "西部决赛", c1["team_id"], c2["team_id"])
# 总决赛
east_c, west_c = out["rounds"].get("conf", {}).get("东部"), out["rounds"].get("conf", {}).get("西部")
if east_c and west_c:
e, w = _winner(east_c), _winner(west_c)
out["rounds"]["final"] = find_match("总决赛", e["team_id"], w["team_id"])
return out
def seasons():
"""可用赛季列表(standings 有数据的),降序"""
rows = query("SELECT DISTINCT season FROM standings ORDER BY season DESC")
return [r["season"] for r in rows]