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 + winnerDelta4) Ничья сейчас даёт 0 изменений.
Пусть у команд одинаковый рейтинг: R_A = 100, R_B = 100.
Тогда expected = 0.5, и:
Δwin = round(32 * (1 - 0.5)) = 16Если A победила, обычно получится: A +16, B -16 (но B не может упасть ниже 10).
Команда 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. Это не про “очки”, а проточность и осмысленные броски.
В Cricket есть 7 целей: 20, 19, 18, 17, 16, 15, Bull.
- Hit — ты попал в одну из целей, и эта цель ещё имеет смысл (она не закрыта у обеих сторон).
- Не важно, это single/double/triple: главное — что бросок был по цели.
- Miss — ты ввёл
0(промах). - Miss — ты попал в цель, которая уже закрыта у обеих сторон. Такой бросок не влияет на игру, поэтому в статистике это промах.
Пример: сектор 20 закрыт у обеих команд, но ты всё равно нажал 20 — это будет miss.
Формула простая:
hitRate% = (hits / throws) * 100Игрок сделал 12 бросков, из них 8 — попадания в рабочие цели.
hitRate% = (8 / 12) * 100 = 66.7%Hit rate — честный показатель стабильности: он не зависит от удачных “случайных” очков и не растёт от бросков в уже закрытые сектора.
Награды (Awards)
Награды за яркие моменты в игрых. Выдаются только по данным LIVE: система смотрит на реальные ходы, попадания и статус секторов (играет / закрыто), и фиксирует достижения автоматически.


















Сезоны: зачем и как работают
В 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 пирожков, доверие к твоему рейтингу намного выше.
Мы используем K², поэтому на дистанции 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 показываются отдельно для прозрачности.