Pull to refresh

Игра для самых маленьких — простая идея, которую не стыдно включить в резюме

Reading time6 min
Views48K

Предыстория


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

КДПВ

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

Чтобы процесс освоения компьютера стал для детёныша более увлекательным, я решил написать ему простенькую игру. Будучи программистом со стажем, весь процесс решено было построить «правильно».

Требования


Заказчик (мой сын, возраст <1 года), как и все нормальные заказчики затруднился письменно изложить непротиворечивые и полные требования к продукту, поэтому пришлось помочь писать самому.

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

Не функциональные:
  • Мне должно быть не стыдно за написанный код.
  • Код должен быть ценен сам по себе.
  • Архитектура и все решения должны быть «правильными» — как в заказном проекте.

Кроме того было принято решение использовать гибкий итеративный подход к разработке с малыми циклами разработки, заканчивающимися получением обратной связи (SCRUM).

В качестве языка программирования и среды разработки были выбраны C# и Visual Studio, так как они обеспечивали исполнителю наибольшую скорость работы.

Реализация


Из одного из старых проектов был извлечен код для создания приложения, развернутого на весь экран:
    FormBorderStyle = FormBorderStyle.None;
    WindowState = FormWindowState.Maximized;
    var screen = Screen.PrimaryScreen;
    Bounds = screen.Bounds;

Далее в дебрях интернета была найдена библиотека MouseKeyHook, с примерами, как заблокировать кнопку Windows. Аналогично примерам были заблокированы Alt-Tab и Ctrl-Esc. Теперь выйти из приложения можно только по Alt-F4.

Далее был написан код, который инициализирует рандомный цвет фона для нажатой клавиши:
  • Использовался new Random(seed), чтобы при каждом запуске рандом выдавал одни и те же значения.
  • Чтобы цвета были более-менее осмысленными, рандом выбирал значение из перечисления KnownColor, которое затем преобразовывалось в Color и присваивалось Form.BackColor.
  • Поддерживались буквенные символы и цифры.
  • Символ выводился «как есть» — клавиша Q могла вывести «Q», «q», «Й», «й», в зависимости от активного языка ввода и состояния CapsLock.

Первые альфа-тесты на себе выявили следующие недостатки реализации:
  • Form.BackColor категорически не согласен принимать цвет Transparent.
  • Чёрный цвет принимается, но символа на нём не видно.
  • Есть ряд клавиш, которые могут быть нажаты, у них есть символ, но они не обрабатываются программой или не отображают символ — Enter, Tab, Space, блок цифр над буквами и блок цифровых клавиш справа на клавиатуре.
  • Очень не нравился код обработки KeyDown/KeyPress — нужно было выделять диапазоны символов 'A-Z' и '0-9', пробел, Enter. Много не очень внятных блоков условий и сложный код расчёта размера массива рандомных цветов и выборки цвета из него.

Во второй итерации были внесены следующие изменения:
  • Написана простенькая WinForm утилита, которая точно так же «слушает» нажатия, сохраняет их в словарь Клавиша-Символ. Это позволило разрешить проблему вывода русских/английских букв.
  • У утилиты есть кнопка сохранения словаря в файл.
  • Поскольку клавиши Space и Enter в этом случае вызывали срабатывание обработчика кнопки, а Tab вызывал переход на кнопку, даже если она не выбрана, пришлось эти случаи отдельно обработать — установить TabStop=false для кнопок и вставить ActiveControl = null везде, где только можно.
  • Утилита помогла выявить все значимые клавиши — она запоминала клавишу при KeyDown, но добавляла её в словарь только по KeyPress, соответственно, всё, что не имеет символьного представления (Alt. Shift, Ctrl, Windows, функциональные клавиши) игнорировалось.
  • Обработку клавиши в самой игре можно будет значительно упростить до поиска по словарю.
  • Формат файла был самый простой — готовые наборы разделяются переводом строки, а поля (Клавиша-Символ-Цвет) в наборе разделяются символом \0 (пробел, табуляцию, и символы вроде запятой использовать не получилось, так как они могли быть элементом набора)
  • После сохранения невидимые символы вручную были заменены на Unicode-символы, отсутствующие на клавиатуре.
  • Цвет подбирался не случайным образом, а брался последовательно из enum KnownColor, начиная со следующего после KnownColor.Black (KnownColor.Transparent идёт немного раньше).

Альфа-тестирование на себе прошло вполне успешно и была проведена демонстрация заказчику.



Заказчик проявил интерес к продукту, выделил целых 2 минуты на тестирование, оценил работу в целом положительно и указал на следующие недостатки:
  • Недостаточная звуковая обратная связь (звук издает только клавиша PrintScreen).
  • Некорректно обрабатывается маленькая светящаяся кнопочка в правом дальнем углу ноутбука (экран гаснет).

Воодушевившись поддержкой заказчика, команда автор провел ретроспективу и сделал следующие выводы:
  • Нужно использовать внешнюю клавиатуру без кнопок управления питанием или маскировать аппаратную кнопку рукой.
  • Пора переходить к звуковой обратной связи.

Для звуковой обратной связи принято решение издавать звуки, соответствующие нотам (клавишам пианино). Быстрый поиск в интернете позволил найти формулу расчета частоты звука для каждой клавиши и данная формула была оперативно реализована в C# коде. Для непосредственного вывода звука на колонки использован Console.Beep (а что, работает же!).
Первый же прогон продемонстрировал недостатки:
  • Автор невнимательно прочитал MSDN, а именно строку «ranging from 37 to 32767 hertz».
  • Низкие звуки примерно до 110 Гц звучат отвратительно и их нельзя показывать заказчику.
  • Длительность звука 300 мс — слишком долго.
  • Звук выводится синхронно и вызывает задержку прорисовки фона.

По результатам были внесены следующие изменения:
  • Формировать частоты от 110Гц (25-я клавиша пианино, A2).
  • Длительность звука сделать 100мс.
  • Выводить звук в отдельном потоке.
  • Команда выразила подозрение, что нужно делать Lock во втором потоке на время выполнения Console.Beep. В дальнейшем подозрение не подтвердилось, но удалять было лень блокировка осталась для дидактических целей.
  • Использовать двойной буфер при смене цвета, чтобы не было полос на экране при быстром нажатии на клавиши.

Данная версия получила высокую оценку самой команды, а поскольку до демо для заказчика оставалось время, команда решила провести рефакторинг:
  • Реализовать паттерн MVC, выделить логику игры в контроллер, во View оставить только код специфичный для работы с формой (переход в полный экран, обработчики событий).
  • Покрыть контроллер юнит-тестами
  • Вынести файл-словарь с тройками «Клавиша-Символ-Цвет» в ресурсы и реализовать русскую и английскую версии.
  • Поскольку на рабочем ноуте (а на нём мы планировали провести демо) у меня стоит локаль английская, было реализована настройка локали через конфиг. При этом в конфиге добавлена своя секция и реализован простенький файл для доступа к этой секции, возвращающий типизированные значения переменных конфига.

Итог


В результате мы получили забавную игрушку для ребенка, которая развивает мелкую моторику и память (на любимые цвета и ноты), а также код, который можно использовать для демонстрации реализации «правильных» подходов. Например, для студентов или Junior-девелоперов.

Вот список того, что можно изучить по коду игрушки:
  • Работа с WinForms (полный экран, двойной буффер, обработка событий клавиатуры)
  • Работа с локализованными ресурсами.
  • Применение паттерна MVC для WinForms (да, да вовсе не обязательно для этого переходить на WPF).
  • Применение паттерна Singletone (многопоточного).
  • Работа с Moq при разработке юнит-тестов.
  • Работа с Shouldly при разработке юнит-тестов.
  • Парсинг строк/файлов.
  • Многопоточность и блокировка потоков.
  • Работа с конфиг-файлом и создание своих секций.
  • Правильный кодинг-стайл и использование комментариев и регионов.
  • Работа с отладочной консолью (логгирование событий).
  • Перечисление значений enum при помощи Enum.GetValues.
  • Работа со статическими методами Array (Copy, IndexOf).
  • Работа с unmanaged-объектами (using).
  • «Отзывчивая» работа формы — подтверждение выхода, использование диалога сохранения файла.
  • Работа с NuGet и выкачивание пакетов при сборке.

Готовый код выложен в виде открытого репозитория на GitHub и доступен с лицензией MIT.

p.s. КДПВ © kobyakov
Tags:
Hubs:
Total votes 83: ↑74 and ↓9+65
Comments41

Articles