from itertools import count from db import get_connection FORMATION_PRESETS = { "4-4-2": { "gk": [{"x": 50, "y": 10}], "def": [ {"x": 18, "y": 28}, {"x": 39, "y": 24}, {"x": 61, "y": 24}, {"x": 82, "y": 28}, ], "mid": [ {"x": 18, "y": 48}, {"x": 39, "y": 44}, {"x": 61, "y": 44}, {"x": 82, "y": 48}, ], "fwd": [ {"x": 38, "y": 70}, {"x": 62, "y": 70}, ], }, "4-3-3": { "gk": [{"x": 50, "y": 10}], "def": [ {"x": 18, "y": 28}, {"x": 39, "y": 24}, {"x": 61, "y": 24}, {"x": 82, "y": 28}, ], "mid": [ {"x": 30, "y": 47}, {"x": 50, "y": 42}, {"x": 70, "y": 47}, ], "fwd": [ {"x": 20, "y": 72}, {"x": 50, "y": 66}, {"x": 80, "y": 72}, ], }, "4-2-3-1": { "gk": [{"x": 0, "y": 0}], "def": [ {"x": 18, "y": 28}, {"x": 39, "y": 24}, {"x": 61, "y": 24}, {"x": 82, "y": 28}, ], "mid": [ {"x": 35, "y": 42}, # опорник {"x": 65, "y": 42}, # опорник {"x": 20, "y": 58}, # левый {"x": 50, "y": 52}, # центр {"x": 80, "y": 58}, # правый ], "fwd": [ {"x": 50, "y": 72}, ], }, "3-5-2": { "gk": [{"x": 50, "y": 10}], "def": [ {"x": 30, "y": 26}, {"x": 50, "y": 22}, {"x": 70, "y": 26}, ], "mid": [ {"x": 10, "y": 50}, # левый фланг {"x": 35, "y": 46}, {"x": 50, "y": 42}, {"x": 65, "y": 46}, {"x": 90, "y": 50}, # правый фланг ], "fwd": [ {"x": 38, "y": 72}, {"x": 62, "y": 72}, ], }, "5-3-2": { "gk": [{"x": 50, "y": 10}], "def": [ {"x": 10, "y": 30}, # левый латераль {"x": 30, "y": 26}, {"x": 50, "y": 22}, {"x": 70, "y": 26}, {"x": 90, "y": 30}, # правый латераль ], "mid": [ {"x": 30, "y": 48}, {"x": 50, "y": 44}, {"x": 70, "y": 48}, ], "fwd": [ {"x": 38, "y": 72}, {"x": 62, "y": 72}, ], }, } def detect_player_line(position: str) -> str: pos = (position or "").strip().lower() if not pos: return "mid" if "вр" in pos or "gk" in pos or "goalkeeper" in pos: return "gk" defender_markers = ["цз", "лз", "пз", "з", "def", "cb", "lb", "rb", "wb"] midfielder_markers = [ "цп", "цоп", "оп", "п", "пзщ", "mid", "cm", "dm", "am", "lm", "rm", ] forward_markers = ["н", "цф", "ф", "lf", "rf", "fw", "st", "cf", "нап"] if any(marker in pos for marker in defender_markers): return "def" if any(marker in pos for marker in midfielder_markers): return "mid" if any(marker in pos for marker in forward_markers): return "fwd" return "mid" def get_match_formations(match_id: int, team_id: int) -> list[dict]: conn = get_connection() try: with conn.cursor() as cur: cur.execute( """ SELECT mf.player_id, mf.player_name, mf.number, mf.position, mf.is_captain, mf.x, mf.y, p.last_name, p.position as position_full FROM match_formations mf LEFT JOIN players p ON p.id = mf.player_id WHERE mf.match_id = %s AND mf.team_id = %s ORDER BY mf.id """, (match_id, team_id), ) rows = cur.fetchall() return [ { "player_id": row[0], "player_name": row[1] or "", "number": row[2] or "", "position": row[3] or "", "position_full": row[8] or "", "is_captain": bool(row[4]), "x": float(row[5]), "y": float(row[6]), "last_name": row[7] or "", } for row in rows ] finally: conn.close() def replace_match_formations(match_id: int, team_id: int, players: list[dict]) -> None: conn = get_connection() try: with conn.cursor() as cur: cur.execute( """ DELETE FROM match_formations WHERE match_id = %s AND team_id = %s """, (match_id, team_id), ) for player in players: cur.execute( """ INSERT INTO match_formations ( match_id, team_id, player_id, player_name, number, position, is_captain, x, y, created_at, updated_at ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW()) """, ( match_id, team_id, player.get("player_id"), (player.get("player_name") or "").strip(), (player.get("number") or "").strip(), (player.get("position") or "").strip(), bool(player.get("is_captain")), float(player.get("x", 50)), float(player.get("y", 50)), ), ) conn.commit() except Exception: conn.rollback() raise finally: conn.close() def parse_position(position: str) -> tuple[str, str]: """ Возвращает: line: gk / def / mid / fwd side: left / center / right """ pos = (position or "").strip().lower() if not pos: return "mid", "center" # Вратарь if "вр" in pos or "gk" in pos or "goalkeeper" in pos: return "gk", "center" # Сторона if pos.startswith("л"): side = "left" elif pos.startswith("п"): side = "right" else: side = "center" # Линия # Защита: ЛЗ, ПЗ, ЦЗ, ЛЦЗ, ПЦЗ и т.п. if "з" in pos: return "def", side # Нападение: Н, Ф, ЦФ, ЛФ, ПФ и т.п. if "ф" in pos or "н" in pos: return "fwd", side # Полузащита: П, ЦП, ЦОП, ЛП, ПП, ПЦП, ЛЦП и т.п. if "п" in pos: return "mid", side return "mid", side def sort_players_by_side(players: list[dict]) -> list[dict]: side_order = { "left": 0, "center": 1, "right": 2, } return sorted( players, key=lambda p: ( side_order.get(parse_position(p.get("position", ""))[1], 1), str(p.get("number", "")), str(p.get("last_name", "") or p.get("player_name", "")), ), ) def apply_formation_preset_to_players( players: list[dict], preset_name: str ) -> list[dict]: preset = FORMATION_PRESETS.get(preset_name) if not preset: raise ValueError(f"Unknown formation preset: {preset_name}") players = players[:11] gk, defs, mids, fwds = [], [], [], [] for p in players: line, _ = parse_position(p.get("position")) if line == "gk": gk.append(p) elif line == "def": defs.append(p) elif line == "mid": mids.append(p) elif line == "fwd": fwds.append(p) else: mids.append(p) defs = sort_players_by_side(defs) mids = sort_players_by_side(mids) fwds = sort_players_by_side(fwds) leftovers = [] def trim_or_collect(group: list[dict], count: int) -> list[dict]: if len(group) > count: leftovers.extend(group[count:]) return group[:count] return group gk = trim_or_collect(gk, len(preset["gk"])) defs = trim_or_collect(defs, len(preset["def"])) mids = trim_or_collect(mids, len(preset["mid"])) fwds = trim_or_collect(fwds, len(preset["fwd"])) # если вратарь не найден — берём первого доступного if not gk: source = defs or mids or fwds or leftovers if source: gk = [source.pop(0)] def fill(group: list[dict], count: int) -> list[dict]: while len(group) < count and leftovers: group.append(leftovers.pop(0)) return group gk = fill(gk, len(preset["gk"])) defs = fill(defs, len(preset["def"])) mids = fill(mids, len(preset["mid"])) fwds = fill(fwds, len(preset["fwd"])) result = [] def assign(group: list[dict], coords: list[dict]): for p, c in zip(group, coords): result.append( { "player_id": p.get("player_id"), "player_name": p.get("player_name") or "", "last_name": p.get("last_name") or "", "number": p.get("number") or "", "position": p.get("position") or "", "is_captain": bool(p.get("is_captain")), "x": c["x"], "y": c["y"], } ) assign(gk, preset["gk"]) assign(defs, preset["def"]) assign(mids, preset["mid"]) assign(fwds, preset["fwd"]) return result