144 lines
6.0 KiB
Python
144 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]
|