Решение владельца: в партии все выбывшие проиграли и одинаково слабы, миров у них нет, поэтому пара двух выбывших в сумму не входит — ни S − E, ни множитель отрыва. Пары с невыбывшими считаются как прежде, нормировка на N − 1 тоже: при равных рейтингах результат не меняется. Слабый выбывший больше не получает рейтинг за счёт сильных выбывших. Эталон — флаг skip_eliminated_pairs у предложенной системы; документ: 4.3, 4.4, 4.9, пример 5, итоги 7.3 пересчитаны (сдвиг в третьем знаке), решение 8 в разделе 9; справка. #91 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
175 lines
10 KiB
TypeScript
175 lines
10 KiB
TypeScript
import { ChevronDown, ChevronUp } from "lucide-react";
|
||
import { useState } from "react";
|
||
|
||
/**
|
||
* Справка — разделы, раскрывающиеся по кнопке. Пока раздел один: «Рейтинг».
|
||
* Текст соответствует backend/app/services/scoring.py и docs/rating/rating-system.md:
|
||
* коэффициенты, пороги и примеры менять вместе с ними.
|
||
*/
|
||
export function HelpPage() {
|
||
const [showRating, setShowRating] = useState(false);
|
||
return (
|
||
<div>
|
||
{/* Название страницы уже в верхней панели — в теле только пояснение. */}
|
||
<div className="card">
|
||
<p className="muted small" style={{ margin: 0 }}>
|
||
Здесь со временем появятся правила, подсказки и пояснения. Пока — о том, как
|
||
считается рейтинг игроков.
|
||
</p>
|
||
</div>
|
||
|
||
{/* Кнопка-карточка раздела — как карточки списка партий. */}
|
||
<button
|
||
className="card"
|
||
style={{ textAlign: "left", width: "100%" }}
|
||
onClick={() => setShowRating((v) => !v)}
|
||
>
|
||
<div className="row-between">
|
||
<h3 style={{ margin: 0 }}>Рейтинг</h3>
|
||
{showRating ? <ChevronUp size={18} /> : <ChevronDown size={18} />}
|
||
</div>
|
||
</button>
|
||
|
||
{showRating && (
|
||
<>
|
||
<div className="card">
|
||
<h3>Рейтинг игроков</h3>
|
||
<p className="small">
|
||
Рейтинг — оценка силы игрока относительно соперников (система Elo). Каждый начинает
|
||
с <b>1500</b>. Разница в <b>400</b> пунктов означает шансы 10 к 1 в пользу более
|
||
сильного.
|
||
</p>
|
||
<p className="small">
|
||
Считаются только <b>завершённые</b> партии, по порядку их игры. Правка или удаление
|
||
старой партии пересчитывает и все последующие.
|
||
</p>
|
||
<p className="small">
|
||
Партия раскладывается на пары игроков. В каждой паре фактический результат (выше —
|
||
1, поровну — 0.5, ниже — 0) сравнивается с ожидаемым по рейтингам до партии:
|
||
</p>
|
||
<div className="formula">ожидание = 1 / (1 + 10^((R соперника − R игрока) / 400))</div>
|
||
<div className="formula" style={{ marginTop: 6 }}>
|
||
ΔR = K × G / (N − 1) × Σ M × (результат − ожидание)
|
||
</div>
|
||
<p className="small" style={{ marginTop: 10 }}>
|
||
где <b>N</b> — число игроков, сумма — по всем соперникам. Поэтому победа над сильным
|
||
приносит больше, чем над слабым, а поражение от слабого отнимает больше. Выбывшие
|
||
делят последнее место, но между собой не сравниваются: в этой партии все они
|
||
проиграли, так что пара двух выбывших рейтинг не двигает.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="card">
|
||
<h3>От чего зависит изменение</h3>
|
||
<ul className="small" style={{ margin: 0, paddingLeft: 18 }}>
|
||
<li>
|
||
<b>K — скорость.</b> У новичка 64, к 20-й партии плавно снижается до 16: новичок
|
||
быстро находит свой уровень, а рейтинг опытного игрока не скачет.
|
||
</li>
|
||
<li>
|
||
<b>G — размер стола:</b> 1 + 0.5 × (N − 2) / 4. В дуэли 1.0, вчетвером 1.25,
|
||
вшестером 1.5.
|
||
</li>
|
||
<li>
|
||
<b>M — отрыв в паре</b>, от 0.5 до 2. Обычная партия даёт 1. Больше, если:
|
||
<ul style={{ paddingLeft: 16 }}>
|
||
<li>
|
||
партия закончилась раньше — для пар с победителем; обычной считается конец
|
||
в предпоследнем раунде;
|
||
</li>
|
||
<li>разница целей велика относительно числа игроков;</li>
|
||
<li>разница миров велика относительно доли миров на игрока.</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<b>Тип победы</b> — для пар с победителем, чем ближе партия, тем меньше:
|
||
по целям ×1, по мирам ×0.85, по пластику ×0.7, по ресурсам ×0.6,
|
||
последний выживший ×1.
|
||
</li>
|
||
</ul>
|
||
<p className="small" style={{ marginTop: 10 }}>
|
||
Раунд окончания, цели и миры вводить <b>необязательно</b>: пропущенное считается
|
||
обычным значением и множитель не меняет. «Последний выживший» ставится сам, когда
|
||
все соперники выбыли, — отрыв по целям тогда максимальный. Лимит раундов — 8,
|
||
с правилом группы «9 раундов» при 5–6 игроках — 9.
|
||
</p>
|
||
<p className="small" style={{ marginBottom: 0 }}>
|
||
Единоличный победитель никогда не теряет рейтинг, а единоличное последнее место
|
||
и выбывание никогда его не приносят. Невыбывшие, поделившие место, сравниваются
|
||
между собой как в ничьей: слабый может получить рейтинг, сильный — потерять.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="card">
|
||
<h3>Примеры (опытные игроки)</h3>
|
||
<div className="stack" style={{ gap: 6 }}>
|
||
<div className="row-between small">
|
||
<span>Дуэль 1600 и 1400: победил сильный</span>
|
||
<b>+3.8 / −3.8</b>
|
||
</div>
|
||
<div className="row-between small">
|
||
<span>Дуэль 1600 и 1400: победил слабый</span>
|
||
<b>+12.2 / −12.2</b>
|
||
</div>
|
||
<div className="row-between small">
|
||
<span>Равные, победа по целям без деталей</span>
|
||
<b>+8.0</b>
|
||
</div>
|
||
<div className="row-between small">
|
||
<span>Равные, победа по мирам без деталей</span>
|
||
<b>+6.8</b>
|
||
</div>
|
||
<div className="row-between small">
|
||
<span>Разгром на 3-м раунде (цели 2:0, миры 8:2)</span>
|
||
<b>+16.0</b>
|
||
</div>
|
||
<div className="row-between small">
|
||
<span>1-е место за столом на 5 равных</span>
|
||
<b>+11.0</b>
|
||
</div>
|
||
<div className="row-between small">
|
||
<span>Новичок обыграл равного опытного</span>
|
||
<b>+32 / −8</b>
|
||
</div>
|
||
</div>
|
||
<p className="muted small" style={{ marginBottom: 0 }}>
|
||
Рейтинг показывается целым числом, но считается без округления.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="card">
|
||
<h3>Один рейтинг на всё приложение</h3>
|
||
<p className="small" style={{ margin: 0 }}>
|
||
Рейтинг у игрока один — по всем его партиям во всех группах, поэтому он одинаковый
|
||
в общем топе, в профиле и на странице группы. На странице группы по партиям этой
|
||
группы считаются только игры, победы, винрейт и среднее место; рейтинг и статус
|
||
«Новичок» там общие.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="card">
|
||
<h3>Попадание в топ</h3>
|
||
<p className="small">
|
||
Чтобы попасть в ранжированный топ, нужно сыграть минимум <b>10 партий</b>. До этого
|
||
игрок показывается в блоке «Новички» — предварительно, без места в рейтинге.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="card">
|
||
<h3>Другие показатели</h3>
|
||
<ul className="small" style={{ margin: 0, paddingLeft: 18 }}>
|
||
<li><b>Винрейт (WR)</b> — доля партий, в которых игрок занял 1-е место.</li>
|
||
<li><b>Среднее место</b> — среднее по завершённым партиям.</li>
|
||
<li><b>Лучшая партия</b> в профиле — та, что принесла больше всего рейтинга.</li>
|
||
<li>
|
||
При равенстве рейтинга топ сортируется по: винрейту, числу игр, среднему месту
|
||
и затем по нику.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|