# Scrabble Game — Функциональная спецификация Пользовательские сценарии по доменам: что делает каждая видимая пользователю операция. Это зеркало [`FUNCTIONAL.md`](FUNCTIONAL.md) для владельца проекта; **авторитетна английская версия**. Любую точечную правку переносим в том же патче (переводим только изменённые абзацы). Разделы наполняются по мере этапов; *(Stage N)* помечает, где пишется детализация. ## Домены ### Клиентское приложение *(Stage 7 / 8)* Веб/приложение-клиент (Svelte + Vite) воплощает эти истории. **Играбельный срез** (Stage 7) покрывает вход (гость или email), лобби «мои игры», старт авто-подбора, игру на доске (постановка фишек перетаскиванием или тапом, пас, обмен, сдача), top-1 подсказку, безлимитную проверку слова с жалобой, чат и nudge в партии, обновления в реальном времени, переключение языка интерфейса (en/ru) и темы и профиль только для чтения. **Stage 8** добавляет управление друзьями (в т.ч. одноразовые коды-приглашения) и блоками, дружеские приглашения в игру, редактирование профиля и привязку email, экран статистики и просмотр истории партии с экспортом GCG. В настройках также выбирается стиль подписей бонус-клеток (новичок / классика / без текста). Подсказка **выставляет предложенные фишки на доску** — игрок сам решает сделать ход, и подсказка не тратится, если ходов нет. Проверка слова принимает только алфавит варианта, запоминает ответы в рамках сессии и ограничивает частоту повторов. Публичная **посадочная страница** в корне сайта представляет игру, переключает язык и тему и ведёт в соответствующий по-язычный Telegram-канал; сама игра живёт по адресам `/app/` (веб) и `/telegram/` (Telegram Mini App). Тема на странице эфемерна (берётся из системной настройки, а не из сохранённой), выбор языка сохраняется. ### Личность и сессии *(Stage 1 / 6 / 9 / 15)* Игрок приходит с платформы (сначала Telegram), через email-вход или как эфемерный гость. Gateway один раз валидирует доступ и выдаёт тонкий session-токен; backend сопоставляет его с внутренним `user_id`. Запуск **Telegram Mini App** авторизует по подписанным `initData` платформы, перекрашивает интерфейс в цвета Telegram и — при первом контакте — задаёт язык интерфейса нового аккаунта по языку Telegram-клиента. Сервис входа также объявляет **языки игры**, которые он предлагает (набор из en/ru, минимум один), и они ограничивают выбор типа партии в лобби. Telegram держит отдельного бота на язык (английский и русский, один игрок охватывает обоих); бот, через которого игрок вошёл, задаёт его доступные языки и является тем ботом, от которого приходят его внеприложенческие уведомления. Гость — только сессия, с урезанными функциями (только авто-подбор; без друзей, статистики и истории); заброшенный гость, не вошедший ни в одну игру и простаивавший дольше окна удержания, удаляется сборщиком. Пока приложение открыто, клиент держит живой стрим и получает обновления в реальном времени — ход соперника, ваш ход, чат, nudge и найденный матч. Когда приложение **закрыто**, выбранные внеприложенческие события (ваш ход, конец партии, nudge, найденный матч, приглашение или заявка в друзья) приходят вместо этого **уведомлением в Telegram** — если только игрок не оставил уведомления только в приложении (настройка профиля, **включена по умолчанию**). Уведомление «ваш ход» называет соперника и пересказывает его последний ход — слово и текущий счёт для результативного хода либо что он поменял фишки или пропустил, — а по завершении партии приходит уведомление «конец партии» с твоим результатом и финальным счётом (счёт читается, твой первым). Если связь пропадает или сервер ограничивает частоту запросов, приложение не донимает ошибками: в шапке тихо крутится спиннер **«Подключение…»**, пока идёт переподключение, действия, отправляющие данные на сервер, приостанавливаются до восстановления связи (экран с серверными данными всё равно открывается — со спиннером — и подгружается при реконнекте), а незавершённые чтения возобновляются сами — интерфейс остаётся рабочим вместо красного баннера каждый раз. ### Аккаунты, привязка и слияние *(Stage 1 / 11)* Первый контакт с платформы заводит постоянный аккаунт. Из профиля игрок привязывает email (по confirm-коду) или свой Telegram (через веб-вход); гость, привязавший первую личность, становится постоянным аккаунтом. Факт «личность уже занята» не раскрывается до проверки кода/входа. Если привязываемая личность уже принадлежит другому аккаунту, игроку показывают явное **необратимое** подтверждение, и два аккаунта сливаются в тот, под которым он сейчас работает (статистика суммируется, игры и друзья переносятся, дубликаты убираются), — кроме случая, когда гость привязывает личность с уже существующим постоянным аккаунтом: тогда сохраняется постоянный аккаунт, а игры гостя переходят в него. Слияние запрещено, только пока у аккаунтов есть общая незавершённая игра. ### Лобби и подбор *(Stage 4 / 15)* Нижнее tab-меню: **мои игры**, **профиль**. Список **мои игры** разбит на три секции — *твой ход*, *ход соперника* и *завершённые* (пустые секции скрыты) — и упорядочен так, что игры, ждущие твоего хода, идут первыми, дольше всего ждущие сверху, а игры на ходу соперника и завершённые — самые свежие сверху; отображается компактным списком с линиями-разделителями (Stage 17). Завершённую партию можно **убрать из своего списка**: проведи по строке завершённой партии влево (или, на десктопе, нажми её **⋮**), чтобы открыть **❌**, и нажми её. Удаление действует только для твоего аккаунта и необратимо — партия исчезает лишь из твоего списка и остаётся в списках других игроков, отмены нет. Типы партий на экране **Новая игра** ограничены языками, которые поддерживает сервис входа игрока (английский → Scrabble; русский → Scrabble + Erudite; двуязычный сервис показывает все три, а веб-клиент не ограничен). Варианты показываются под **отображаемым именем** — оба варианта Scrabble читаются как «Scrabble»/«Скрэббл», а Erudit — «Erudite»/«Эрудит» (по языку интерфейса), и это же имя выносится в заголовок экрана игры. Это ограничивает только **старт** новой игры — и авто-подбор, и приглашение друга, — поэтому игрок по-прежнему видит и играет существующие игры на любом языке. Авто-подбор (всегда 2 игрока) встаёт в пул по варианту и сводится со следующим ожидающим человеком; через 10 с без человека подставляется робот (робот — в Stage 5). Игры с друзьями (2–4) формируются приглашением игроков из списка друзей (приглашение, как и код друга, можно отправить deep-link'ом в Telegram, который откроет его сразу): инициатор выбирает настройки, и партия стартует, когда приняли все приглашённые — любой отказ отменяет приглашение, а без ответа приглашение протухает через семь дней. ### Игровой процесс *(Stage 3)* Выкладывание фишек, пас, обмен или сдача. Ход проверяется по словарю партии при сдаче и считается; безлимитный предпросмотр сообщает, сколько принёс бы предполагаемый ход и легален ли он. Инструмент проверки слова безлимитный и предлагает пожаловаться на любой результат. Подсказки управляются настройками партии — разрешены ли они и сколько их у каждого игрока на старте — и расходуют личный кошелёк подсказок после исчерпания внутриигрового лимита. Партия завершается, когда мешок пуст и игрок выложил стойку, после 6 подряд бесплодных ходов, по сдаче, либо по таймауту хода (от 5 минут до 24 часов, дефолт 24 часа): пропущенный ход означает авто-сдачу, кроме как когда игрок внутри своего суточного окна отсутствия (away). В партии на двоих сдача или таймаут отдают победу другому игроку, а вышедший сохраняет свои очки. В партии на троих-четверых место вышедшего убирается, остальные играют дальше, и партия завершается, когда остаётся один активный игрок; что делать с фишками вышедшего (вернуть в мешок или убрать из игры) выбирается при создании партии, а его стойка никогда не показывается остальным. **Композиция на доске сохраняется по партии**: расположение фишек на стойке и выложенные, но не отправленные фишки сохраняются по мере составления хода и восстанавливаются при возврате (в том числе на другом устройстве); игрок может **раскладывать фишки и в ход соперника**, но такой черновик только позиционный — предпросмотр счёта и отправка доступны лишь в собственный ход. ### Робот-соперник *(Stage 5)* Если авто-подбор не находит человека за десять секунд, свободное место занимает робот-соперник, и партия стартует без ожидания. Он задуман неотличимым от человека: один раз за партию решает, играть ли на победу (примерно в 40% случаев, так что человек выигрывает большинство партий), целится в близкий счёт, а не в разгром или поддавки, и ходит с человеческим темпом — чаще короткие раздумья, изредка долгие, и ночная пауза, подстроенная под день игрока. На nudge отвечает за несколько минут и сам шлёт nudge, когда игрок надолго пропал. Носит человекоподобное имя, подходящее языку партии (в русской партии — в основном русские имена); не общается в чате и **молча игнорирует заявки в друзья** — заявка роботу остаётся в ожидании и истекает, ровно как у человека, который не отвечает. ### Социальное: друзья, блок, чат, nudge *(Stage 4 / 8)* Подружиться можно двумя способами: погасить **одноразовый код**, который выпускает другой игрок (шесть цифр, действует двенадцать часов), либо отправить **заявку тому, с кем вы играли** — он принимает, игнорирует (заявка истекает через тридцать дней, после чего её можно отправить снова) или отклоняет (отказ блокирует ваши повторные заявки, пока он сам не передаст вам код). Отмена своей висящей заявки снимает её; удаление расторгает дружбу. В партии пункт меню **в друзья** для каждого соперника отражает живое отношение: он показывает *заявка отправлена* (неактивный), пока заявка висит или была отклонена, и *в друзьях* после принятия — обновляясь на месте в момент ответа соперника и оставаясь верным после перезагрузки (Stage 17). Глобальная блокировка — отключить входящие чат и/или заявки — и блокировка конкретного игрока (пер-юзер блок скрывает его чат и запрещает заявки и приглашения в игру в обе стороны, а также расторгает уже имеющуюся дружбу). Чат партии — для быстрых реакций: сообщения короткие (до 60 символов) и не должны содержать ссылок, email и телефонов, даже завуалированных. Nudge ожидаемого соперника — не чаще раза в час (nudge — часть игрового чата); внеприложенческий push доставляется через платформу. Чат и инструмент проверки слова открываются **отдельными экранами** (с кнопкой «назад» в партию), а новое сообщение в чате рисует **бейдж непрочитанного** на меню партии до открытия чата. ### Профиль и настройки *(Stage 4 / 8)* Редактирование отображаемого имени (буквы, разделённые одиночным пробелом / «.» / «_», с необязательной завершающей «.», до 32 символов и не более 5 спецсимволов — пунктуации «.» / «_», пробелы не в счёт), таймзоны (выбор смещения от UTC), суточного окна отсутствия (away; сетка по 10 минут, не более 12 часов, с переходом через полночь) и переключателей блокировок. Форма профиля редактируется сразу (без отдельного режима редактирования). Привязка email и Telegram, а также слияние аккаунтов вынесены в раздел «Аккаунты, привязка и слияние» (Stage 11). ### История и статистика *(Stage 3 / 8)* Завершённые партии архивируются в независимом от словаря виде и экспортируются в GCG; экспорт доступен **только после завершения партии** (экспорт идущей партии раскрыл бы журнал ходов), и клиент делится файлом `.gcg` там, где платформа это поддерживает, иначе скачивает его. Статистика (только у постоянных аккаунтов): победы, поражения, ничьи, макс. очков за партию и макс. очков за один ход (лучший ход, уже включающий все образованные им слова и бонус за все фишки). ### Администрирование *(Stage 10)* Оператор открывает серверную админ-консоль по адресу `${DOMAIN}/_gm` — её рендерит backend; gateway закрывает её HTTP Basic Auth на публичном порту и проксирует один-в-один. В консоли можно смотреть **пользователей** (профиль, статистика, identity, их игры) и **игры** (сводка + места), разбирать **очередь жалоб на слова** — закрывая каждую как reject / accept-add / accept-remove — и управлять **словарём**: резидентные версии по вариантам, **горячая перезагрузка** новой версии из `BACKEND_DICT_DIR//` и **список ожидающих правок**, выведенный из принятых жалоб (он питает офлайн-пересборку и отмечается применённым после перезагрузки). Если подключён Telegram-коннектор, оператор также может **написать пользователю** (по его Telegram-identity) или **отправить пост в игровой канал**. Изменяющие действия защищены проверкой same-origin; личность оператора не отслеживается.