Содержание

  1. Общая архитектура
  2. API-эндпоинты soccerlife.ru
  3. Переменная _inf — метаданные матча
  4. Формат moves.php — движения и события
  5. Коды действий (action codes)
  6. Траектория мяча
  7. Игровые события (labels)
  8. Наши эндпоинты
  9. Поток данных

1. Общая архитектура

SoccerLife AI Assist перехватывает и анализирует данные матча с soccerlife.ru в реальном времени. Данные поступают из двух источников:

  Browser                 soccerlife.ru
     │
     ├── GET /game.php?id=XXXXX  ──►  HTML-страница матча
     │        └── переменная _inf (JSON)
     │             ├── Состав обеих команд с side=1/2
     │             ├── Цвета формы, имена, номера
     │             └── Метаданные матча (время старта, минута)
     │
     └── GET /ajax/moves.php?game_id=X&minute=Y  ──►  JSON
              ├── Позиции всех игроков (каждый ~1с)
              ├── Траектория мяча
              ├── Позиция арбитра + игровые события
              └── Покрывает следующие 5 игровых минут
  
💡 Ключевое преимущество
moves.php возвращает данные на 5 игровых минут вперёд. Матч идёт в реальном времени, но мы уже знаем все события следующих 5 минут: штрафные, угловые, голы, замены — ещё до того как они произойдут на экране.

2. API-эндпоинты soccerlife.ru

game.php — страница матча

GET https://soccerlife.ru/game.php?id={game_id}

HTML-страница. Содержит встроенный JavaScript с переменной _inf (подробнее в разделе 3). Авторизация не требуется для просмотра.

moves.php — позиции и события

GET https://soccerlife.ru/ajax/moves.php?game_id={game_id}&minute={minute}
ПараметрТипОписание
game_idintID матча из URL
minuteint 0–90Стартовая игровая минута блока (кратно 5)

Возвращает JSON с позициями всех сущностей на следующие ~5 игровых минут.

Ответ:

{
  "status":      1,           // 1 = OK
  "next_minute": 41,          // когда запрашивать следующий блок
  "check_time":  1774698554,  // unix timestamp сервера
  "time":        1774698599,  // время обработки запроса
  "moves": {
    "ENTITY_ID": ["frame1", "frame2", ...],
    ...
  }
}

game4.php — параллельные результаты

GET https://soccerlife.ru/ajax/game4.php?action=live_results&game_id={id}&type=1

Возвращает HTML-таблицу с результатами других матчей, идущих параллельно в том же чемпионате. Полезно для отслеживания турнирной ситуации.

3. Переменная _inf — метаданные матча

Встроена в HTML как _inf = {...} (второе присваивание — JSON-формат).

Структура верхнего уровня

ПолеТипОписание
startstring (unix ts)Время начала матча
durationintДлительность в секундах (2040 ≈ 34 мин)
in_progressboolИдёт ли матч сейчас
minuteintТекущая игровая минута
participantsobjectВсе сущности матча (см. ниже)
lineupintФлаг состава (0/1)
hlsarrayHLS-стримы (обычно пусто)
game_urlstringURL матча

Объект participants[PLAYER_ID]

{
  "type":         1,           // 1=игрок, 2=мяч (entity "7"), 3=арбитр (entity "89")
  "side":         1,           // 1=хозяева, 2=гости
  "kit_id":       3629,        // ID комплекта формы
  "kit_color":    "#4747eb",   // основной цвет формы
  "text_color":   "#fff",      // цвет номера
  "circle_color": "#fff",      // цвет подложки
  "position":     "AM",        // амплуа (GK, CD, DM, CM, AM, ST, LD, RD, LW, RW, LM, RM, LB, RB)
  "name":         "Йилмаз",   // фамилия игрока
  "number":       "90"         // номер на спине
}
📌 Определение команд
Поле side — единственный надёжный источник для разделения команд. В данных moves.php все игроки кодируются одинаково и команду из них не определить.

Дополнительно из JS-переменной game_inf:

ПолеОписание
id1Roster ID команды хозяев (ссылка на /roster.php?id=)
id2Roster ID команды гостей
unixUnix timestamp старта матча
hp_finishUnix timestamp предполагаемого конца матча
techdefТехническое поражение: -1=нет, 1=хозяева, 2=гости

4. Формат moves.php — движения и события

Ключи объекта moves

КлючТип сущностиОписание
"7"МячПозиция и траектория мяча. Команда = 0
"89"АрбитрПозиция арбитра + текстовые аннотации игровых событий
"XXXXXXX"Игрок6-8 значный ID игрока

Формат одного кадра (frame)

Каждый элемент массива — строка вида:

"duration:action:x:y:direction[:label]"
ПолеТипОписание
durationfloatДлительность кадра в секундах анимации
actionintКод действия (см. раздел 5)
xfloatКоордината X на поле (0–104)
yfloatКоордината Y на поле (0–69). -10 = вне поля (запас)
directionint Для игроков: направление спрайта (1–4).
В ПЕРВОМ кадре (action=69): номер команды (0/1/2).
Для мяча: всегда 0.
labelstring?Опциональная текстовая аннотация события

Первый кадр (INITIAL) — особый формат

"0:69:39:28:2:am"   → action=69, начальная позиция, команда=2, амплуа=am
"699.2:69:52:-10:2" → запасной игрок (y=-10)
⚠️ Тайминг: начальный кадр не учитывается
Первый кадр (action=69) имеет искусственно большое значение duration (~700–1300с) — это серверный плейсхолдер, не реальное время. Все временны́е расчёты ведутся начиная со второго кадра (elapsed=0).

Система координат поля

(0, 0) (104, 0) (0, 69) (104, 69) (52, 34) → X (ширина) ↑ Y GK хоз. x≈9 GK гост. x≈99

5. Коды действий (action codes)

Универсальные

КодНазваниеОписание
69INITIALНачальный кадр позиции. Только первый кадр каждой сущности. Duration — плейсхолдер
1JUMP/KICKКороткий стоп/прыжок/контакт с мячом. Игрок останавливается

Коды игроков

КодОписание
5Бег/ходьба — основное движение по полю
20Сильный удар / длинный пас
4Падение/подкат/столкновение

Коды мяча (entity "7")

КодНаправлениеОписание
2↙ / ↖Качение по земле (левое направление спрайта)
4↗ / ↘Качение по земле (правое направление спрайта)
3ВысокаяВоздушная траектория — удар, навес, заброс, вбрасывание из аута
20ДальняяМощный удар / выбивание вратарём
22РестартМяч установлен в точке возобновления игры (угловой, штрафной, от ворот)
1СтопМяч остановился / минимальное движение
💡 Определение ударов по воротам
Удар по воротам — когда мяч с action=3 или action=20 движется в сторону ворот (x→0 или x→104) с высокой скоростью (duration < 2с на большое расстояние).

6. Траектория мяча

Entity "7" содержит полную траекторию мяча каждого 5-минутного блока. Анализируя её, можно определить:

СитуацияПризнак
Мяч вышел за линиюy=-10 или x=-10 (вне поля)
Рестарт игрыaction=22 после позиции вне поля
Удар / навесaction=3 (воздушная траектория)
Сильный ударaction=20
Владение у хозяевavg_x мяча < 52
Владение у гостейavg_x мяча > 52
Атака хозяевмяч движется к x≈99 (ворота гостей)
Атака гостеймяч движется к x≈9 (ворота хозяев)

Пример сырых данных мяча

"699.4:69:52:4:0"    — стартовая позиция, центр поля
"0.9:2:58:4:0"       — качение вправо
"1:4:58:10:0"        — качение вправо вверх
"1.3:3:39:4:0"       — воздушная траектория (навес/удар)
"0.8:2:-10:26:0"     — мяч вышел за линию (x=-10)
"7.7:20:-10:26:0"    — мяч ещё вне поля (вратарь берёт)
"0:22:6:42:0"        — мяч установлен у ворот (от ворот / угловой)
"1:20:6:42:0"        — удар от ворот

7. Игровые события (labels)

Метки-события появляются только у entity "89" (арбитр). Игроки в moves.php меток событий не имеют — только арбитр объявляет события.

⚠️ Дедупликация
Некоторые события (особенно ГОЛ!) сервер повторяет в 2–3 соседних кадрах. Мы убираем дубли: если два события одного типа идут с разницей менее 10 секунд — оставляем первое.
Метка (Russian)event_typeИконкаОписание
ГОЛ!goalГол. Сервер шлёт 3 раза подряд — дедуплицируется
штрафнойfree_kick🟡Штрафной удар
угловойcorner🔵Угловой удар
вне игрыoffside🚩Офсайд (также офсайд)
от воротgoal_kick🥅Удар от ворот
в аутthrow_in↩️Вбрасывание из аута
пенальтиpenalty🔴Пенальти
жёлтаяyellow_card🟨Жёлтая карточка (также *yc*)
краснаяred_card🟥Красная карточка
заменаsubstitution🔄Замена игрока
фолfoul🦵Нарушение
устное пр.verbal_warning🗣️Устное предупреждение
на граниdangerous_tackle⚠️Опасная игра
штангаcrossbar🔔Попадание в штангу/перекладину
поехали!kickoff▶️Начало / рестарт после гола (старт)

Определение скорера гола

В moves.php нет прямой метки «кто забил». Применяем эвристику:

  1. Определяем атакующую команду по позиции мяча у ворот соперника
  2. Берём координату ворот соперника (x ≈ 9 или x ≈ 99)
  3. Находим игрока атакующей команды, ближайшего к этим воротам в момент гола
⚠️ Точность ~80%
Эвристика даёт правильный результат в большинстве случаев, но может ошибиться при автоголах, добиваниях или сложных игровых ситуациях.

8. Наши эндпоинты (web_app.py)

МетодURLОписание
GET / Главная страница с интерфейсом
GET /docs Эта страница документации
GET /info/{game_id} Метаданные матча: составы, цвета, текущая минута
GET /chunk/{game_id}/{minute} Одиночный разбор чанка в JSON (для отладки)
GET /stream/{game_id}?minute=N&cookie=... SSE-стрим: живые данные матча. События приходят в реальном времени

SSE события потока /stream/{game_id}

eventКогдаPayload
game_infoОдин раз при стартеСоставы, цвета, текущая минута
chunkКаждые ~5 минПолные данные блока: игроки, мяч, все события
game_eventВ реальном времениОдно событие в момент его наступления
heartbeatКаждые 15сПинг для поддержания соединения
errorПри ошибкеСообщение об ошибке
doneКонец матчаМатч завершён

9. Поток данных

  Запуск стрима (/stream/{game_id})
       │
       ▼
  fetch_game_info(game_id)          ← GET game.php → _inf JSON
       │
       ├── team membership (side 1/2)
       ├── player names & positions
       └── kit colors

  SSE → event: game_info            ← клиент получает составы и цвета
       │
       └── LOOP: minute = start_minute → 90
              │
              ▼
         fetch moves.php?minute=N
              │
              ▼
         parse_chunk()
              ├── _parse_entity() × 40 сущностей
              │     ├── Первый кадр (action=69) → стартовая позиция, амплуа
              │     └── Остальные кадры → анимация, duration без initial frame
              │
              └── _extract_events()
                    ├── Пропускаем initial frame (duration — плейсхолдер)
                    ├── Собираем labeled frames от entity "89"
                    ├── Пропускаем метки амплуа (cd, dm, gk...) как non-events
                    └── Дедуплицируем повторяющиеся события (< 10с интервал)

  SSE → event: chunk               ← клиент рендерит игроков на поле
       │                              и показывает все события как "pending"
       │
       └── LOOP: событие за событием
              │
              ▼
         sleep(event.second - elapsed)
              │
              ▼
         SSE → event: game_event   ← клиент активирует pending → fired
  

Файлы проекта

ФайлРоль
models.pyДатаклассы: MoveFrame, EntityTrack, GameEvent, MatchChunk. EVENT_MAP, Action коды
parser.pyРазбор строк moves.php, извлечение событий, дедупликация
game_info.pyПарсер _inf из game.php — составы, команды, цвета
fetcher.pyHTTP-клиент, LiveMatchPoller (background thread), CLI
web_app.pyFastAPI: SSE-стрим, сериализация, определение скорера гола
main.pyCLI: одиночный разбор или живой режим в терминале
static/index.htmlSPA: SVG-поле, лента событий, анимация, pending/fired система