Let's Darts! · О проекте и рейтинге

Это клубная система учёта игр, рейтинга и прогресса игроков в дартсе. Рейтинг работает корректно только при честном вводе результатов.

Что такое Let's Darts!

Let's Darts! — это система, которая хранит историю игр и считает рейтинг игроков и команд. У нас есть:

  • общий рейтинг (Total) и сезонный рейтинг (Season),
  • командные игры 1×1, 2×2, 3×3,
  • LIVE-игра со статистикой точности (hit rate),
  • награды (Awards) за достижения по ходу игры,
  • уровни игрока (Level) — это прогресс по количеству сыгранных игр.

Клубы: как устроены и как ими пользоваться

Теперь в Let's Darts! появились клубы. Клуб отделяет одну компанию игроков от другой и задает общий контекст для игр, рейтингов, сезонов и статистики.

Что дает клуб
  • у каждого клуба свой состав игроков и своя история игр,
  • активный клуб определяет, какие игры, рейтинги и таблицы вы видите на сайте,
  • внутри клуба можно вести собственные сезоны, лидерборды и приглашать новых участников.

Один и тот же аккаунт может состоять в нескольких клубах, но работать на сайте вы будете в рамках одного активного клуба.

Как начать работу с клубами
  • Если у вас еще нет клуба, на странице /clubs можно создать свой клуб.
  • Если клуб уже существует, можно вступить в него по invite token или по полной invite-ссылке.
  • После вступления клуб становится активным, и вы сразу видите его игроков, игры, рейтинг и статистику.
Как взаимодействовать с клубом каждый день
  • Переключайте активный клуб, если состоите сразу в нескольких клубах.
  • Владельцы клуба могут управлять названием, invite-ссылкой и настройками клуба.
  • Приглашайте новых игроков через invite-ссылку: после перехода по ней участник сможет вступить в клуб и открыть его как активный.

Идея простая: клуб это отдельное игровое пространство со своей жизнью, а аккаунт это ваш общий вход в систему, который может использоваться сразу в нескольких сообществах.

Честный подход (важно)

Рейтинг — это доверие. Система считает цифры, но “справедливость” зависит от участников клуба:

  • Игра фиксируется только если он действительно сыгран.
  • Победитель/счёт серии вводятся честно.
  • Ничьи отмечаем честно (и сейчас они дают 0 изменения рейтинга).
  • Любые “подкрутки” (например, договорные серии) быстро ломают смысл рейтинга и мотивацию.

Если хотите “пофаниться без влияния на рейтинг” — лучше сыграть неранговый игра (или договориться, что игра не заносим).

Рейтинг на примере «пирожков»

Представим, что рейтинг — это пирожки (очки репутации). После каждого игры пирожки перераспределяются: победитель получает пирожки, проигравший — теряет, но есть защита “не опускаемся ниже минимума”.

Почему так?
  • Если ты обыгрываешь сильного соперника — система “удивлена” и даёт больше пирожков.
  • Если выигрывает явный фаворит — это ожидаемо, награда меньше.
  • Система zero-sum: сколько прибавили победителю — столько же забрали у проигравшего (с учётом минимального пола).

Максимальный “шаг” за игра задаётся коэффициентом K = 32, но реальная дельта почти всегда меньше, потому что учитывается ожидаемость победы.

Формулы и шаги расчёта

Старт и минимальный рейтинг

У нас есть два важных ограничения:

  • Стартовый рейтинг: START_TOTAL_RATING = 100 и START_SEASON_RATING = 100. Это единая “точка отсчёта”, чтобы новичок сразу был в системе и сравнение было корректным.
  • Минимальный рейтинг: MIN_RATING = 10. Это защита от ситуации, когда игрок/команда “падает в ноль”, и дальше любые игры становятся математически странными и демотивирующими.

Рейтинг можно проигрывать, но ниже минимума мы не опускаемся. В обычных игрых zero-sum сохраняется настолько, насколько это возможно: если проигравший упёрся в минимум, списание режется.

1) Сначала считаем ожидаемую вероятность победы по разнице рейтингов (Elo-подобная модель):

expected = 1 / (1 + 10^((R_opp - R_me) / 400))

2) Если команда победила, считаем положительную дельту:

Δwin = round(K * (1 - expected))
Δwin >= 1  (минимум 1 за победу)

3) Дальше применяется zero-sum и защита от ухода ниже минимума MIN_RATING = 10:

loserAfter  = max(MIN_RATING, loserBefore - Δwin)
loserDelta  = loserAfter - loserBefore   (<= 0)

winnerDelta = -loserDelta                (строго zero-sum)
winnerAfter = winnerBefore + winnerDelta

4) Ничья сейчас даёт 0 изменений.

Пример 1: равные соперники

Пусть у команд одинаковый рейтинг: R_A = 100, R_B = 100.

Тогда expected = 0.5, и:

Δwin = round(32 * (1 - 0.5)) = 16

Если A победила, обычно получится: A +16, B -16 (но B не может упасть ниже 10).

Пример 2: фаворит против андердога

Команда A сильная: R_A = 1600, команда B слабее: R_B = 1200.

Для A ожидаемость победы будет высокой (~0.909), поэтому:

Δwin(A) = round(32 * (1 - 0.909)) ≈ 3

Фаворит выигрывает — получает мало (ожидаемо). Но если выигрывает B — тогда ожидаемость для B низкая (~0.091) и:

Δwin(B) = round(32 * (1 - 0.091)) ≈ 29

Сенсация = большой прирост.

Команды 1×1 / 2×2 / 3×3

В Let's Darts! рейтинг считается на уровне команд, а затем (если в команде больше одного игрока) распределяется по игрокам.

  • 1×1: “игрок = команда”. Для 1×1 мы жёстко синхронизируем: player.rating == solo-team.rating.
  • 2×2 / 3×3: сначала считается командная дельта, потом распределяется по игрокам внутри команды.
Как делится командная дельта между игроками

Мы используем взвешенное распределение: сильные игроки получают чуть меньшую долю, слабые — чуть большую. Логика веса:

weight_i = avgRating / rating_i

Затем дельты округляются и “доводятся” так, чтобы сумма совпала с командной, и применяется защита MIN_RATING при отрицательных дельтах.

LIVE: Hit rate (точность попаданий)

В режиме LIVE мы показываем hit rate — насколько часто ты попадаешь в нужные цели Cricket. Это не про “очки”, а проточность и осмысленные броски.

Что считается попаданием (hit)

В Cricket есть 7 целей: 20, 19, 18, 17, 16, 15, Bull.

  • Hit — ты попал в одну из целей, и эта цель ещё имеет смысл (она не закрыта у обеих сторон).
  • Не важно, это single/double/triple: главное — что бросок был по цели.
Что считается промахом (miss)
  • Miss — ты ввёл 0 (промах).
  • Miss — ты попал в цель, которая уже закрыта у обеих сторон. Такой бросок не влияет на игру, поэтому в статистике это промах.

Пример: сектор 20 закрыт у обеих команд, но ты всё равно нажал 20 — это будет miss.

Формула простая:

hitRate% = (hits / throws) * 100
Мини-пример

Игрок сделал 12 бросков, из них 8 — попадания в рабочие цели.

hitRate% = (8 / 12) * 100 = 66.7%

Hit rate — честный показатель стабильности: он не зависит от удачных “случайных” очков и не растёт от бросков в уже закрытые сектора.

Награды (Awards)

Награды за яркие моменты в игрых. Выдаются только по данным LIVE: система смотрит на реальные ходы, попадания и статус секторов (играет / закрыто), и фиксирует достижения автоматически.

Слепень
Слепень
5 подходов без результата подряд.
Серия из 5 ваших подходов подряд без результата: промах или попадание в закрытую цифру. Между ними могут быть ходы других игроков.
Слепой Пью
Слепой Пью
10 подходов без результата за матч.
В сумме 10 ваших подходов без результата в течение матча (не обязательно подряд). Промах или попадание в закрытую цифру.
Закрыватель
Закрыватель
Закрыл 3 цифры соперника.
Закрыл 3 цифры соперника в течение матча.
Утилизатор
Утилизатор
Закрыл 5 цифр соперника, не дав ему заработать.
Как «Закрыватель», но дополнительно не дав сопернику заработать на открытой цифре.
Открыватель
Открыватель
Открыл 3 цифры.
Открыл 3 цифры в течение матча.
Утроитель
Утроитель
5 утроений за игру.
Любые 5 утроений в работающую цифру за матч. Если цифра уже была закрыта и не играет — не считается.
Удвоитель
Удвоитель
5 удвоений за игру.
Любые 5 даблов в работающую цифру за матч. Если цифра уже была закрыта и не играет — не считается.
Меткий глаз
Меткий глаз
3 утроения подряд.
Три утроения за ОДИН ваш подход. Не серия по разным ходам — именно 3 тройника в текущем подходе.
Волшебник
Волшебник
3 утроения одной цифры за подход.
За один ваш подход вы попали ТРИЖДЫ утроением в одну и ту же работающую цифру (например T20+T20+T20). Если цифра не играет — не считается.
Булито
Булито
3 удвоения BULL за матч.
Считаются только играющие попадания в DB. Если BULL закрыт, не работает — не засчитывается.
Джеронимо
Джеронимо
5 удвоений BULL за матч.
Считаются только играющие попадания в BULL. Пять штук за матч.
Булотино
Булотино
5 раз попал в BULL за матч.
Считаются только играющие попадания в BULL: B или DB, но только если цифра работает.
Золотой сухарь
Золотой сухарь
Выиграл матч, не дав сопернику заработать.
Вы победили, закрыли все цифры, а у соперника 0 очков.
Черствый сухарь
Черствый сухарь
Проиграл матч, не заработав ни одного очка.
Вы проиграли и не заработали очков.
Звёздный мальчик
Звёздный мальчик
Набрал 100 очков за подход.
Набрали 100 или более очков на работающих цифрах в одном подходе.
Лаки бой
Лаки бой
Попал в 3 разные цифры за подход.
Три разные работающие цифры за один ваш подход. B и DB — это одна цифра (BULL).
Полуночник
Полуночник
Сыграл live-матч после 23:00.
Выдаётся за live-матч, если первый бросок был между 23:00 и 06:00 по Москве.
Колумб
Колумб
Первым открыл BULL за один подход.
Вы открыли BULL за один подход (≥3 попадания: B=1, DB=2). До начала вашего подхода в матче не было ни одного попадания в BULL (у всех 0).

Сезоны: зачем и как работают

В Let's Darts! есть два типа рейтинга:

  • Total — общий рейтинг за всё время. Он никогда не сбрасывается и показывает “силу в целом”.
  • Season — сезонный рейтинг. Он нужен, чтобы устраивать “новый старт” внутри клуба и соревноваться в рамках конкретного периода (месяц, квартал, год).
Что происходит при создании сезона

Когда вы создаёте новый сезон:

  • Season начинает считаться заново внутри сезона: сезонные лидерборды и статистика идут отдельно для выбранного периода.
  • Total остаётся как есть — он всегда хранит историю за всё время.

Идея простая: Total отвечает на вопрос “кто сильнее в целом”, а Season — “кто лучший в этом сезоне”.

В статистике и лидербордах вы увидите оба значения. Это удобно: новичок может быстро проявиться в сезоне, даже если в Total пока небольшой опыт.

Уровни игрока

Уровень — это не рейтинг. Это “прогресс/опыт” по количеству сыгранных игр. Он не зависит от побед/поражений и нужен для мотивации и красивого профиля.

Пороговые значения уровней
  • 1 — Новичок: 0–10 игр
  • 2 — Ученик: 11–20
  • 3 — Любитель: 21–50
  • 4 — Уверенный: 51–80
  • 5 — Продвинутый: 81–100
  • 6 — Полупрофи: 101–130
  • 7 — Мастер: 131–180
  • 8 — Эксперт: 181–220
  • 9 — Элита: 221–300
  • 10 — Легенда: 301+

Эти пороги — UI-конфиг (levels.ts). Если клуб решит, их можно менять.

Очки сезона (⚡️)

Очки (⚡️) — это сезонный показатель, по которому сортируется таблица игроков. Он нужен, чтобы игрок с высоким рейтингом, сыгравший всего 1–2 игры, не обгонял тех, кто стабильно играет весь сезон.

От чего зависят Очки

Очки сезона зависят от двух вещей:

  • Сезонный рейтинг (ratingSeason) — насколько сильным игрок показал себя по результатам игр сезона.
  • Количество игр в сезоне (gamesSeason) — сколько игр он реально сыграл в этом сезоне.

Идея простая: рейтинг — это “качество”, а игры — это “подтверждённый опыт”. Мы взвешиваем одно другим, усиливая влияние дистанции.

Сначала считаем коэффициент опыта сезона:

K = ln(gamesSeason + 1) / ln(maxGamesSeason + 1)

Где maxGamesSeason — максимальное количество игр в сезоне среди всех игроков.

Затем считаем Очки сезона:

Score = ratingSeason × K²
Объяснение «на пирожках»
  • Рейтинг — насколько вкусные у тебя пирожки (качество).
  • Игры — сколько пирожков ты реально испёк в этом сезоне (опыт).
  • Очки (⚡️) — сколько “вкусных пирожков” ты принёс клубу за сезон.

Один супер-пирожок — это круто, но если ты печёшь стабильно 40–50 пирожков, доверие к твоему рейтингу намного выше.

Мы используем , поэтому на дистанции 1–15 игр “случайный” высокий рейтинг почти не влияет на таблицу. При большой дистанции K → 1, и Очки становятся близки к рейтингу.

Мини-пример

Пусть лидер сезона сыграл maxGamesSeason = 60.

Игрок A (новичок): ratingSeason = 140, gamesSeason = 5

K = ln(6) / ln(61) ≈ 0.44
Score ≈ 140 × 0.44² ≈ 27

Игрок B (стабильный): ratingSeason = 120, gamesSeason = 40

K = ln(41) / ln(61) ≈ 0.90
Score ≈ 120 × 0.90² ≈ 97

В таблице выше будет игрок B — он сыграл много, и его рейтинг подтверждён дистанцией.

Важно:

  • Очки считаются только по сезону.
  • В начале сезона лидеры видны быстро, но случайные всплески “на 2 игрых” больше не ломают таблицу.
  • Чем больше игр — тем ближе K → 1, и тогда Очки становятся почти равны рейтингу.

В списке игроков рядом с аватаркой отображаем ⚡️ Очки сезона — именно по ним сортируется таблица. Рейтинги Season и Total показываются отдельно для прозрачности.