Skip to content

Latest commit

 

History

184 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📡 MorseWave

Русский · 🌐 English

Тёмный минималистичный тренажёр азбуки Морзе Буквы по отдельности · Метод Коха · Группы символов · Реальные слова · Позывные · XP и достижения

PHP MySQL JS License

🌐 morse.r9old.ru — живая версия


Что это

MorseWave — веб-тренажёр азбуки Морзе, который проведёт от полного нуля до приёма на слух реальных позывных и подготовки к сдаче радиолюбительского экзамена (в «Группах» есть режим экзамена по нормативам «Первой категории»). Без фреймворков на фронтенде и без Bootstrap: чистый JS (Web Audio API) + собственная тёмная дизайн-система.

Прогресс (XP, уровень, серия дней, выученные символы) хранится локально в браузере — регистрация не обязательна, тренироваться можно сразу. Аккаунт — опциональная надстройка, которая даёт три вещи: место в таблице лидеров на главной (только по явной кнопке «Опубликовать»), синхронизацию всего прогресса между устройствами (начал на компьютере — продолжил с телефона) и восстановление доступа по почте. MySQL под капотом хранит справочные данные (достижения, банк позывных), анонимную статистику сообщества, аккаунты и серверные копии прогресса.

Интерфейс двуязычный (русский / английский) — переключатель в шапке рядом с профилем, подробности в разделе «Как это устроено внутри».

Возможности

Раздел Что внутри
📖 Буквы Три подрежима: отправка ключом (тап/пробел, с живым сайдтоном; три набора — «Латиница + цифры», порядок Коха, кириллица), приём на слух (играет символ → нажми на экранной клавиатуре; отдельный набор «Кириллические символы») и ритм ключа (сравнивает реальные длительности точки/тире/пауз с идеалом 1:3:1 — скорость свободно регулируется, идеал пересчитывается под неё; 5 точных повторов подряд оттачивают ритм буквы и дают разовый XP; наборы — «Латиница + цифры» и кириллица, без порядка Коха — он там ни при чём)
👾 Вторжение (BETA) Мини-игра-тауэр-дефенс рядом с «Буквами»: пришельцы летят на базу с буквой на слух, символ надо распознать и нажать на самодельной QWERTY-клавиатуре (или на физической клавиатуре компа) до того, как враг долетит — с базы летит лопата. Скорость врага зависит от сложности кода символа, до 5 одновременных целей по ходу волны из 100 попаданий, победная волна даёт бонус XP от остатка HP базы и скорости реакции
🎯 Метод Коха Символы сразу на целевой скорости; новый символ открывается при точности ≥90%; можно вручную установить любое число открытых символов (в обе стороны)
🔢 Группы символов Приём на слух групп по 2–5 символов: буквы / цифры / микс / только выученные / свои, скорость и интервалы Фарнсворта — слайдерами; режим экзамена по нормативам радиолюбительской категории
📝 Реальные слова Приём частых английских слов и радиообменных фраз (CQ CQ DE R9OGL K, UR RST 599 599): тренирует узнавание слова единым звуковым образом, а не сборку по буквам. Третий таб в «Группах»
💬 Сокращения Q-коды и радиолюбительские сокращения (CQ, QTH, 73…) на слух с вариантами ответа
📡 Позывные Тренировка на реальных по формату позывных (включая российские форматы), банк расширяется скриптом-генератором
🏆 Достижения Ачивки, условия хранятся в БД (achievements) — можно добавлять новые без правки кода
🎯 Задание дня Целевое задание под этап игрока (новичку — «изучи буквы», дальше — «прими на слух», знающему алфавит — группы) + бонус +50 XP раз в день
💡 Сигнальная лампа Визуальный аналог звука — можно тренироваться совсем без звука
🏆 Таблица лидеров Топ-10 на главной по опыту и по серии дней + отдельной строкой настоящее место залогиненного пользователя, если он не попал в десятку; отдельная страница со всей таблицей целиком (leaderboard.php), публикация — только по явной кнопке «Опубликовать»
👤 Аккаунт Опционально: синхронизация прогресса между устройствами с индикатором «когда синхронизировано», восстановление пароля по почте, самостоятельное удаление аккаунта
💾 Бэкап прогресса Скачать/загрузить прогресс файлом на странице профиля — аккаунт не нужен. Импорт сливает данные «только вверх», поэтому старый файл поверх нового прогресса безопасен
⚠️ Сброс прогресса Кнопка на странице «Достижения» — полный сброс (localStorage и серверной копии) с подтверждением
🌐 Русский / English Переключатель языка в шапке; письма, юридические страницы и API-ошибки тоже переведены

Стек

  • PHP 8+ — без фреймворка, простой роутинг по файлам
  • MySQL / MariaDB — через PDO
  • Vanilla JS — Web Audio API для звука, без сборки и npm
  • Кастомный CSS — дизайн-система в assets/css/style.css, без Bootstrap
  • Docker — для локальной разработки (PHP+Apache, MySQL, phpMyAdmin)

Запуск и деплой

Коротко — локально через Docker:

git clone <URL_РЕПО> morse-trainer
cd morse-trainer
docker compose up -d --build

Сайт на http://localhost:8080, phpMyAdmin на http://localhost:8081.

Полные инструкции — запуск без Docker, шпаргалка по командам, деплой на VPS, миграции базы, настройка почты и администрирование (кто становится админом и как передать права) — в DEPLOY.md.

Структура проекта

morse-trainer/
├── docker-compose.yml       # локальный запуск: PHP+Apache, MySQL, phpMyAdmin
├── docker/                  # Dockerfile + конфиг Apache (AllowOverride для .htaccess)
├── README.md                 # этот файл — обзор проекта на русском (основной, так видят GitHub-посетители по умолчанию)
├── README.en.md               # обзор проекта на английском
├── DEPLOY.md                  # запуск, деплой, миграции, администрирование
├── CLAUDE.md                 # контекст и правила для Claude Code
├── CHANGELOG.md               # история изменений, v2.49+ (свежие записи сверху)
├── CHANGELOG-archive.md       # история изменений, v2.48 и старше
├── lang/
│   ├── ru.php                # словарь строк интерфейса — русский
│   └── en.php                 # словарь строк интерфейса — английский
├── api/                     # JSON-эндпоинты: achievements, callsigns, stats, register,
│                             #   login, logout, me, sync_progress, refresh_published_stats,
│                             #   leaderboard, verify_email, resend_verification, captcha,
│                             #   admin_users, admin_rename_user, admin_delete_user,
│                             #   admin_set_admin, push_progress, pull_progress,
│                             #   delete_progress, delete_account, request_password_reset,
│                             #   reset_password, update_account, unpublish_stats
├── config/
│   ├── database.example.php # шаблон конфига БД (коммитится)
│   ├── database.php         # реальный конфиг БД с кредами (в .gitignore)
│   ├── mail.example.php      # шаблон конфига почты (коммитится)
│   ├── mail.php               # реальный конфиг почты с ключом (в .gitignore)
│   └── .htaccess               # запрет веб-доступа к папке
├── database/
│   ├── schema.sql            # ЕДИНСТВЕННЫЙ источник истины по схеме БД
│   ├── seed_callsigns.php    # генератор пачки позывных
│   └── .htaccess              # запрет веб-доступа к папке
├── includes/                 # header / footer / nav + auth.php, mailer.php, i18n.php,
│                             #   resend_mailer.php, captcha.php, leaderboard_guard.php
├── storage/sessions/         # файлы PHP-сессий (закрыты .htaccess, в git не хранятся)
├── assets/
│   ├── css/style.css         # дизайн-система
│   └── js/                   # morse-data, daily, audio, progress, signal, input, i18n + страницы
├── index.php                 # главная + задание дня
├── leaderboard.php            # вся таблица лидеров целиком
├── learn.php                 # буквы: отправка / приём на слух / ритм ключа
├── koch.php                  # метод Коха
├── groups.php                # группы символов 2–5 (+ реальные слова, сокращения)
├── callsigns.php              # тренировка позывных
├── achievements.php           # достижения + сброс прогресса
├── account.php                 # профиль (вкладки: аккаунт / звук / отображение / бэкап)
├── terms.php · privacy.php     # пользовательское соглашение и политика конфиденциальности
├── admin.php                    # админка: пользователи, переименование, удаление, выдача прав
└── settings.php                # 301-редирект на account.php (настройки переехали в профиль, v2.24)

За что даётся опыт (XP)

Все начисления округляются до целых. Уровень растёт как 1 + √(XP / 80) (т.е. 2-й уровень — 80 XP, 3-й — 320 XP, 5-й — 1280 XP и так далее).

Режим Сколько Условия и защита от фарма
Буквы → отправка ключом +25 XP за символ Один раз за символ — когда впервые набрана серия из 5 верных повторов подряд. Повторные серии по выученному символу XP не дают. Ставка одинаковая для латиницы и кириллицы — без надбавки за сложность (осознанное упрощение, v2.51.1).
Буквы → приём на слух +1 XP за верный ответ Спам верной плиткой в паузе между раундами заблокирован. Ставка одинаковая для латиницы и кириллицы (в v2.51.1 кириллица недолго давала +2, но это путало — одинаково выглядящие латинская и кириллическая «M» получали разный XP; откачено в v2.51.7).
Буквы → ритм ключа +25 XP разово за букву Та же анти-фарм схема, что и у отправки ключом: XP не капает за отдельные попытки, а выдаётся один раз, когда набрано 5 точных повторов подряд (буква верная И точность ритма ≥ 80% от идеала 1:3:1). Любая осечка обнуляет серию; повторная тренировка уже отточенной буквы — без XP.
Вторжение (BETA) (База 1 XP) × 2 × (Плотность волны / 3) (как в Кохе), + бонус ×2 за добивание Босса. Бонус победы волны (10–130 XP). Награда растет соразмерно плотности волны (количеству пришельцев на экране), используя балансную формулу из Метода Коха/Групп, чтобы предотвратить фарм опыта. Боссы (группы символов) при добивании дают дополнительный опыт, равный их длине ×2. Бонус победы начисляется только за полностью пройденную волну (100 попаданий).
Метод Коха 2 × (символов_открыто / 15, но не более 1 и не менее 0.15) × (длина_группы / 3) XP за каждый верно принятый символ Чем больше открыто символов и длиннее группы — тем выше ставка (максимум 2 XP/символ при полном наборе). На старте с 2 символами ставка минимальная — фармить лёгкие сессии невыгодно.
Метод Коха — бонус +10/+20/+30 XP (равен числу групп в сессии) Только если сессия завершена целиком с точностью ≥ 90 %. Ачивки за метод считаются по честно заработанному уровню (kochLevelEarned) — протащить бегунок до конца и получить ачивку нельзя.
Группы символов 2 × √(размер_набора / 26, но не более 1 и не менее 0.15) × (длина_группы / 3) XP за каждый верно принятый символ Ставка зависит от размера выбранного набора и длины групп. Начисляется сразу за каждую отвеченную группу.
Группы — повтор ошибок Опыт x3 от базового + бонус 5 XP за безошибочную группу Генерируются новые случайные группы с замешиванием символов, в которых была ошибка (если перепутал две буквы — в группу попадут обе). Бонус работает как анти-абуз: начисляется только если точность в оригинальной сессии была ≥ 60%. Если меньше (умышленный проклик пустых ответов) — XP за ошибки не начисляется (0 XP).
Группы — экзамен ставка сессии × верные символы Только если экзамен доигран до конца — досрочная остановка обнуляет XP экзамена (в общие счётчики группы всё равно идут).
Реальные слова 1.2 XP за символ (слова) / 1.5 XP (фразы) Ставка ниже, чем в группах: связный текст предсказуем, недослышанную букву можно восстановить по смыслу. Если слово принято хуже чем на 60 % — XP за него не начисляется вовсе (иначе банк слов фармился бы вслепую). Пробелы во фразах в счёт не идут.
Сокращения (Q-коды) +5 XP за верный ответ
Позывные +20 XP за верно принятый позывной Начисляется сразу, не дожидаясь конца сессии.
Задание дня +50 XP бонусом Один раз в день. Задание целевое под этап игрока (см. daily.js): новичку — «изучи 3 новые буквы», осваивающемуся — «прими 20 символов на слух», знающему алфавит — приём групп. Для группового задания параметры сессии должны совпадать с заданием и точность быть не ниже 50 % (прокликать пустые ответы не выйдет, фикс v2.26).
Бонус за скорость (только в задании дня) множитель XP speedXpFactor(wpm): ≤12 wpm → ×1.0, выше — +4 %/wpm, потолок ×1.6 (≈27 wpm) Применяется только к групповому заданию дня, где скорость навязана. В обычных сессиях, «Реальных словах» и Кохе не действует — там скорость выбирает сам человек. Смысл: не заставлять новичка гнаться за 24 wpm ради тех же 50 XP, но честно доплачивать за высокий темп. Фарма нет — XP по-прежнему только за верный символ, а быстро принять сложнее.

Почему формулы в Кохе и Группах разные

Выглядит как недосмотр, но это осознанное решение (v2.33) — не сводите их к одной.

В Группах набор символов выбирает сам человек, поэтому маленький набор обязан давать пропорционально меньше XP. Прежний делитель 15 упирался в потолок слишком рано: свой набор из 15 самых лёгких знаков давал ровно ту же ставку, что полный алфавит из 36, — это была работающая лазейка. Делитель 26 (полный латинский алфавит) её закрывает, а корень вместо линейной пропорции нужен, чтобы честные промежуточные наборы вроде «Цифры» (10 знаков) не штрафовались слишком жёстко. Числа подобраны так, что оба основных пресета — «Буквы» (26) и «Буквы + цифры» (36) — дают прежние 2.0 XP/символ: баланс существующих режимов не поехал.

В Кохе набор задан уровнем, а не выбором человека. Пофармить маленьким набором там нельзя в принципе: для этого пришлось бы сидеть на низком уровне, который и так даёт минимальную ставку. Поэтому формула осталась старой — ужесточение не закрыло бы ни одной лазейки, зато срезало бы мотивацию на средних уровнях метода.

Серия дней (🔥 streak) засчитывается только за реальную завершённую тренировку (сессия Коха/групп/позывных или задание дня) — просто зайти на сайт недостаточно.

Как это устроено внутри

  • assets/js/audio.jsMorseAudio проигрывает точки/тире через осциллятор Web Audio API, поддерживает интервалы Фарнсворта (символы на одной скорости, паузы — на другой, более медленной). onSymbol колбэк отдаёт durationMs — используется для точной синхронизации визуализации.
  • assets/js/input.jsTelegraphKey превращает удержание клавиши (тап по экрану или пробел) в точки/тире по длительности нажатия относительно текущего wpm, плюс живой сайдтон (реальный звук) во время удержания. Пороги: < 2×unit → точка, иначе тире; пауза 2.5×unit — граница буквы, ещё +4×unit — граница слова.
  • assets/js/daily.jsDailyChallenge: единый источник «задания дня» для главной и режимов-исполнителей, задание считается из пары «дата + этап игрока».
  • assets/js/signal.jsSignalLine (осциллограф из точек/тире) и MorseLamp (визуальная лампа) — сквозные визуальные элементы, синхронно отражающие и звук, и ввод на всех страницах тренировки.
  • assets/js/progress.js — весь прогресс пользователя в одном ключе localStorage (morsewave_progress_v1): XP, уровень, серия дней, выученные символы, уровень Коха, статистика, разблокированные достижения. Progress.resetAll() — полный сброс.
  • Синхронизация и её индикатор. Progress.pushNow() шлёт весь Progress-объект в api/push_progress.php и при ответе ok: true запоминает момент в отдельном ключе morsewave_last_sync + шлёт событие progress:synced; account.js рисует по нему строку «☁ Синхронизировано: 5 минут назад». Метка ставится только при реальном успехе.
  • Бэкап файломProgress.exportBackup() / Progress.importBackup(). Импорт намеренно идёт через тот же mergeFromServer(), что и слияние при логине: «только вверх» (max по числам, union по множествам). Поэтому загрузить старый файл поверх свежего прогресса безопасно.
  • Достижения определяются в MySQL (achievements, поле condition_type
    • condition_value), а проверка условий против статистики происходит на клиенте в Progress.checkAchievements() — сопоставление типов см. в progress.js (statValue()).
  • Защита лидерборда (includes/leaderboard_guard.php) — перед публикацией в user_stats значения обрезаются по правдоподобному потолку (привязан к возрасту аккаунта), отсекая грубую накрутку. Реальный прогресс не меняется — режется только публичная строка (подробности в CHANGELOG, v2.40).
  • Интернационализация (includes/i18n.php, lang/ru.php, lang/en.php, assets/js/i18n.js) — язык выбирается по ?lang= → cookie mw_langAccept-Language браузера → по умолчанию русский. Хелпер t('ключ') доступен и в PHP-шаблонах, и (как глобальная функция t()) в JS через window.MW_I18N, который includes/header.php кладёт в <head>. Фолбэк при отсутствующем переводе: текущий язык → русский → сам ключ. Слоговые мнемоники букв (MORSE_MNEMONICS и CYRILLIC_MNEMONICS в morse-data.js) — фонетическая подсказка, завязанная на русское звучание, поэтому в английском интерфейсе она осознанно отключена, а не переведена дословно.
  • Кириллическая азбука Морзе (learn.php, v2.51) — отдельная таблица на 33 кода (CYRILLIC_CODE/CYRILLIC_TO_CHAR в morse-data.js, в обучающем наборе 31 буква — Ъ и Ё исключены из тренировок, чтобы не путать с Е и не загромождать процесс редкими знаками), намеренно не смешанная с латинской MORSE_CODE/MORSE_TO_CHAR: часть кириллических букв совпадает по коду с «похожей по звучанию» латинской (так исторически устроена сама азбука), поэтому общий реверс-словарь сделал бы декодирование ключом непредсказуемым. TelegraphKey (input.js) принимает таблицу декодирования параметром/через setTable(), а не жёстко зашитым глобалом. Прогресс по кириллице хранится с префиксом RU_ (learnedLetters) — латинская A и кириллическая А никогда не пересекаются, а ачивка «Полный алфавит» считается строго по латинице+цифрам (progress.js), несмотря на то что кириллические записи лежат в том же массиве.

История изменений

Актуальная история (v2.49 и новее) — в CHANGELOG.md, свежие записи сверху. Более ранние версии, вплоть до самой первой, — в CHANGELOG-archive.md.

Известные ограничения / открытые вопросы

  • Аккаунты — опциональная надстройка над localStorage (не обязательны для тренировки). Клиент остаётся источником истины по XP, поэтому абсолютной защиты от накрутки нет; публичный лидерборд прикрыт серверным санитарным потолком (leaderboard_guard.php, v2.40) — он отсекает грубые значения, но это «замок от честных людей», а не пуленепробиваемая защита. Один и тот же прогресс из одного браузера технически можно опубликовать от нескольких аккаунтов — барьером служит email-подтверждение + мягкое предупреждение при повторной публикации.
  • Письма шлются через Resend API (см. DEPLOY.md) — на shared-хостинге SMTP оказался ненадёжен (нет DKIM, PTR общего IP не указывает на домен, Gmail тихо режет письма).
  • Капча на азбуке Морзе (includes/captcha.php) — базовая защита от примитивных скриптов, не от целенаправленного обхода.
  • TelegraphKey калибрует точку/тире относительно выбранного wpm, а не адаптируется к реальному темпу пользователя.
  • Кириллица есть только в «Буквах» (отправка + приём на слух) — «Метод Коха» и «Группы символов» остаются латинскими. Это осознанно, не недосмотр: у Коха фиксированный классический порядок символов, который по определению латинский, а Группы/режим экзамена ориентированы на программу радиолюбительского экзамена (латиница+цифры) — кириллический набор там просто не на что было бы натянуть.

Roadmap / идеи для развития

Порядок примерно = приоритет (подробности и нюансы — в CLAUDE.md):

  • Свой адаптивный декодер приёма на слух — подстраивается под реальную скорость передачи по таймстемпам, а не под выбранный wpm
  • Адаптивная калибровка ключа под реальный темп пользователя
  • (дальний прицел) Нативное Android-приложение (не PWA)
  • (исследование) Подключение реального телеграфного ключа (адаптер на Arduino / Web Serial / микрофонный вход)

Уже сделано: восстановление пароля по почте, экспорт/импорт прогресса файлом, целевые задания дня, самостоятельное удаление аккаунта, защита лидерборда, двуязычный интерфейс (русский/английский), кириллическая азбука Морзе в «Буквах» (v2.51), полная таблица лидеров + своё место на главной (v2.52), оценка ритма ключа в «Буквах» (v2.53), мини-игра «Вторжение» — тауэр-дефенс на азбуку Морзе (v2.54, статус BETA), достижения за волны вторжения и за отточенный ритм ключа (v2.55–v2.56).

Смежный проект

Освоил приём отдельных знаков и позывных — дальше по маршруту (см. «Путь новичка» на главной) идёт Morse Walker: симулятор радиосвязи с pile-up, режимы контестов и POTA, генератор российских позывных. Это отдельный проект, не часть MorseWave — исходный автор W6NYC, доработка и русификация — тоже R9OGL: github.com/loloka/morsewalker.

Лицензия

MIT — делай с этим что хочешь, см. LICENSE.

About

Dark minimalistic Morse code (CW) web trainer from scratch to on-air speeds. Koch method, Farnsworth timing, rhythm trainer, callsigns, Q-codes, Latin & Cyrillic, mini-game.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages