diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index 99ab836..871edfe 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -6,6 +6,7 @@ import { AccountPage } from "../pages/AccountPage"; import { CreateMatchPage } from "../pages/CreateMatchPage"; import { GroupPage } from "../pages/GroupPage"; import { GroupSettingsPage } from "../pages/GroupSettingsPage"; +import { HelpPage } from "../pages/HelpPage"; import { HomePage } from "../pages/HomePage"; import { LoginPage } from "../pages/LoginPage"; import { MatchDetailPage } from "../pages/MatchDetailPage"; @@ -52,6 +53,7 @@ export const router = createBrowserRouter([ ), }, { path: "account", element: }, + { path: "help", element: }, ], }, { diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx index a079818..0b2460a 100644 --- a/frontend/src/components/AppShell.tsx +++ b/frontend/src/components/AppShell.tsx @@ -15,6 +15,7 @@ const TITLES: Record = { "/account": "Аккаунт", "/match/new": "Новая партия", "/stats": "Общая статистика", + "/help": "Справка", }; export function AppShell() { diff --git a/frontend/src/components/SideMenu.tsx b/frontend/src/components/SideMenu.tsx index 70abd08..f9215e3 100644 --- a/frontend/src/components/SideMenu.tsx +++ b/frontend/src/components/SideMenu.tsx @@ -1,4 +1,4 @@ -import { LogOut } from "lucide-react"; +import { HelpCircle, LogOut } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useLogout, useMe } from "../hooks/auth"; @@ -18,6 +18,11 @@ export function SideMenu({ open, onClose }: { open: boolean; onClose: () => void navigate("/account"); }; + const goHelp = () => { + onClose(); + navigate("/help"); + }; + const doLogout = async () => { onClose(); await logout.mutateAsync().catch(() => {}); @@ -41,6 +46,9 @@ export function SideMenu({ open, onClose }: { open: boolean; onClose: () => void )}
+ diff --git a/frontend/src/pages/HelpPage.tsx b/frontend/src/pages/HelpPage.tsx new file mode 100644 index 0000000..06d0ff1 --- /dev/null +++ b/frontend/src/pages/HelpPage.tsx @@ -0,0 +1,76 @@ +/** + * Справка. Пока — только как считаются очки игроков (рейтинг). В дальнейшем сюда + * добавятся правила, подсказки и прочее. Текст соответствует backend/app/services/scoring.py. + */ +export function HelpPage() { + return ( +
+
+

Справка

+

+ Здесь со временем появятся правила, подсказки и пояснения. Пока — о том, как + считается рейтинг игроков. +

+
+ +
+

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

+

+ Рейтинг считается только по завершённым партиям. Незавершённые и отменённые + в зачёт не идут. +

+

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

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

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

+
    +
  • Если несколько игроков делят место (ничья) — они делят сумму очков поровну.
  • +
  • Выбывшие из партии считаются как последнее место.
  • +
+

+ Итоговый рейтинг игрока = среднее «очков за место» по всем его партиям × 100 + (шкала от 0 до 100). +

+
+ +
+

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

+
+
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 каждому +
+
+
+ +
+

Попадание в топ

+

+ Чтобы попасть в ранжированный топ, нужно сыграть минимум 3 партии. До этого + игрок показывается в блоке «Новички» — предварительно, без места в рейтинге. +

+
+ +
+

Другие показатели

+
    +
  • Винрейт — доля партий, в которых игрок занял 1-е место.
  • +
  • Среднее место — среднее по завершённым партиям.
  • +
  • + При равенстве рейтинга топ сортируется по: винрейту, числу игр, среднему месту + и затем по нику. +
  • +
+
+
+ ); +} diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 4d2440d..9937cb1 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -94,6 +94,18 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } .error-text { color: var(--danger); font-size: 13px; } +/* Формула в справке */ +.formula { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 10px 12px; + text-align: center; + color: var(--text); +} + /* Модальное окно подтверждения */ .modal-overlay { position: fixed;