diff --git a/docs/rating/rating-system.md b/docs/rating/rating-system.md index 1d6c78e..175fe5b 100644 --- a/docs/rating/rating-system.md +++ b/docs/rating/rating-system.md @@ -1,7 +1,7 @@ # Новая рейтинговая система -> **Статус:** предложение на утверждение (#22). Решения владельца по открытым вопросам -> (2026-09-14) внесены — [раздел 9](#9-решения-владельца). Реализация — #23. +> **Статус:** утверждено (#22), реализовано в #23 (`backend/app/services/scoring.py`). +> Решения владельца по открытым вопросам (2026-09-14) внесены — [раздел 9](#9-решения-владельца). > **Приложение:** [`simulate.py`](simulate.py) — эталонная реализация формул, все примеры > этого документа (с `assert`), симуляция и перебор коэффициентов. Только стандартная > библиотека Python, вывод воспроизводим: diff --git a/frontend/src/components/ProfileStatsCard.tsx b/frontend/src/components/ProfileStatsCard.tsx index 496d9e2..c2df55b 100644 --- a/frontend/src/components/ProfileStatsCard.tsx +++ b/frontend/src/components/ProfileStatsCard.tsx @@ -35,7 +35,7 @@ export function ProfileStatsCard({ {o.avg_place ?? "—"}
-
Очки (рейтинг)
+
Рейтинг
{o.score ?? "—"}
{/* Любимая — личный выбор игрока в профиле; ниже — статистика по партиям. */} diff --git a/frontend/src/pages/HelpPage.tsx b/frontend/src/pages/HelpPage.tsx index e7fe227..aebfefc 100644 --- a/frontend/src/pages/HelpPage.tsx +++ b/frontend/src/pages/HelpPage.tsx @@ -2,8 +2,9 @@ import { ChevronDown, ChevronUp } from "lucide-react"; import { useState } from "react"; /** - * Справка — разделы, раскрывающиеся по кнопке. Пока раздел один: «Рейтинг» - * (как считаются очки игроков). Текст соответствует backend/app/services/scoring.py. + * Справка — разделы, раскрывающиеся по кнопке. Пока раздел один: «Рейтинг». + * Текст соответствует backend/app/services/scoring.py и docs/rating/rating-system.md: + * коэффициенты, пороги и примеры менять вместе с ними. */ export function HelpPage() { const [showRating, setShowRating] = useState(false); @@ -32,50 +33,113 @@ export function HelpPage() { {showRating && ( <>
-

Очки игроков (рейтинг)

+

Рейтинг игроков

- Рейтинг считается только по завершённым партиям. Незавершённые и отменённые - в зачёт не идут. + Рейтинг — оценка силы игрока относительно соперников (система Elo). Каждый начинает + с 1500. Разница в 400 пунктов означает шансы 10 к 1 в пользу более + сильного.

- За каждую партию игрок получает «очки за место» — они нормированы по числу игроков - за столом, поэтому победа за большим столом ценится выше, чем за маленьким: + Считаются только завершённые партии, по порядку их игры. Правка или удаление + старой партии пересчитывает и все последующие.

-
- очки = (N − место − (размер_ничьей − 1) / 2) / (N − 1) +

+ Партия раскладывается на пары игроков. В каждой паре фактический результат (выше — + 1, поровну — 0.5, ниже — 0) сравнивается с ожидаемым по рейтингам до партии: +

+
ожидание = 1 / (1 + 10^((R соперника − R игрока) / 400))
+
+ ΔR = K × G / (N − 1) × Σ M × (результат − ожидание)

- где N — число игроков в партии. Первое место даёт 1.0, последнее — 0.0. -

- -

- Итоговый рейтинг игрока — сглаженное среднее: к реальным партиям - «дописываются» 10 виртуальных со средним результатом 0.5 (шкала от 0 до 100): -

-
- рейтинг = (10 × 0.5 + сумма очков) / (10 + число партий) × 100 -
-

- Пока партий мало, рейтинг держится около 50 и с опытом сходится к реальному - среднему — стабильные результаты на длинной дистанции ценятся выше короткой - удачной серии. + где N — число игроков, сумма — по всем соперникам. Поэтому победа над сильным + приносит больше, чем над слабым, а поражение от слабого отнимает больше. Выбывшие + делят последнее место.

-

Пример (стол на 4 игроков)

+

От чего зависит изменение

+ +

+ Раунд окончания, цели и миры вводить необязательно: пропущенное считается + обычным значением и множитель не меняет. «Последний выживший» ставится сам, когда + все соперники выбыли, — отрыв по целям тогда максимальный. Лимит раундов — 8, + с правилом группы «9 раундов» при 5–6 игроках — 9. +

+

+ Победитель никогда не теряет рейтинг, а последнее место никогда его не приносит. +

+
+ +
+

Примеры (опытные игроки)

-
1-е место(4−1−0)/3 = 1.00 → 100
-
2-е место(4−2−0)/3 = 0.67 → 67
-
3-е место(4−3−0)/3 = 0.33 → 33
-
4-е место(4−4−0)/3 = 0.00 → 0
- Ничья за 1-е (двое)(4−1−0.5)/3 = 0.83 каждому + Дуэль 1600 и 1400: победил сильный + +3.8 / −3.8 +
+
+ Дуэль 1600 и 1400: победил слабый + +12.2 / −12.2 +
+
+ Равные, победа по целям без деталей + +8.0 +
+
+ Равные, победа по мирам без деталей + +6.8 +
+
+ Разгром на 3-м раунде (цели 2:0, миры 8:2) + +16.0 +
+
+ 1-е место за столом на 5 равных + +11.0 +
+
+ Новичок обыграл равного опытного + +32 / −8
+

+ Рейтинг показывается целым числом, но считается без округления. +

+
+ +
+

Общий и групповой рейтинг

+

+ Общий рейтинг считается по всем партиям приложения, групповой — только по партиям + группы. Поэтому числа в общем топе и в топе группы различаются. +

@@ -91,6 +155,7 @@ export function HelpPage() {
{entries.length === 0 ? (
diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index 6854a04..0980e98 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -115,7 +115,7 @@ /* Таблица общей статистики (сортируемая, кликабельные строки) */ .st-row, .st-head { display: grid; - grid-template-columns: 26px 1fr 40px 40px 48px 52px; + grid-template-columns: 26px 1fr 40px 48px 60px; align-items: center; gap: 8px; padding: 8px 2px;