first commit

This commit is contained in:
2026-04-20 11:56:11 +03:00
commit 8d80fadb56
103 changed files with 19756 additions and 0 deletions

View File

@@ -0,0 +1,369 @@
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
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 "",
"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