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

Предыстория


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

КДПВ

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

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

Требования


Заказчик (мой сын, возраст <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

Средняя зарплата в IT

110 450 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 7 043 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 41

    +4
    Вы — молодец! Тоже подумывал сделать нечто подобное — ребёнок (1 год от роду) тоже очень любит клавиатуру :)
    +1
    [offtop]
    Точно — статья «из песочницы». )))
    P.S. Поздравления молодому отцу )
    [/offtop]
      +6
      Я, конечно, параноик, но очень уж активно все офтальмологии говорят не смотреть детям в экран хотя бы до 2х лет.
        0
        Детей привлекает все движущееся и новое, на экране этого добра может быть валом. Вредно смотреть долго на объект на одном расстоянии, в мониторе же нет глубины? Поэтому лучше дозировать или ограничить до возраста, когда детки могут тебя слушать и не смотреть любимый мультик, когда попросишь.
          +26
          т.е. лет до тридцати? ))
            +1
            А то и больше )
          0
          К сожалению, если почти все взрослые, которые окружают нашего малыша смотрят в экран и тыкают по клавиатуре, очень сложно будет объяснить, что ему так делать не стоит. Понятно, что родительское внимание и воспитание нельзя заменить компьютером, но:
          • Больше, чем на 2-3 минуты его всё равно не хватает
          • Можно «вести себя как папа» и никто ему ничего не скажет ;)
          • Развивается мелкая моторика
          • Ребёнку стало интереснее нажимать на кнопки, а не выковыривать их (я замучался, если честно, вставлять на место ноутбучные кнопки)
          • Старшие дети заинтересовались программированием, будем учиться геймдеву :)
            0
            Правильно говорите, но сейчас «экран» больше ассоциируется со смартфонами и планшетиками, кои позволяют быстро и дешево успокоить и/или отвлечь ребёнка.

            Вот как тут поступать уже зависит исключительно от родителей. Я своему решительно запрещаю брать телефоны и планшет, а мелкую моторику гораздо лучше в песочнице развивать. А правильно профессии ещё успею научить =)
              0
              Лучше игрушки дайте, чем будет в экран пялится.
              У меня сыну 2.5, мультики по телеку смотрим минут по 15 в день, а то и меньше. Иногда на компе показываем. Нажимать клавиши разрешаем на выключенном компе, на включенном ребенок хорошо понимает что может что то там сломать.

              А для моторики купите конструкторы.
            +4
            Со стороны UI есть предложения:
            — увеличить размер символов (до 70% экранной высоты)
            — менять цвет символов на темном фоне
              0
              — добавить плавную смену цветов, чтобы не вызвать эпилептический припадок
              +2
              В свое время приделал русский для своего мелкого вот к этому — github.com/shanselman/babysmash

                +2
                Иногда даже полезно дать клавиатуру ребёнку.
                Дочка в 2 года случайно нашла комбинацию в Skype — изображение кота при нажатии одновременно c+a+t
                А в айпаде нашла комбинацию как делать скриншоты. Пришлось потом гуглить, чтобы понять как она это сделала :)
                  +2
                  Мой сын на заблокированном экране включил Narrator и ноут озвучивал нажатые клавиши. Несколько раз я отчетливо услышал «Unknown key».

                  А в одной из промежуточных версий описываемой программы выводился сам символ, который введен на клавиатуре, так он смог ввести символ «многоточие».

                  После этого анекдот про бесконечно много обезьян и «Войну и мир» приобретает новые смыслы ;)
                    0
                    Любые 3 кнопки рядом вроде раньше было чтобы показать котика
                      0
                      Кот в skype появляется при беспорядочном нажатии на клавиши, и означает что по клавиатуре прошлась кошка, или пользователь не в себе.
                      0
                      Интересное утверждение про регионы и правильный кодинг-стайл :)

                      Если честно, я не знаю ни одного нормального аргумента за регионы.
                      blog.codinghorror.com/the-problem-with-code-folding
                      programmers.stackexchange.com/questions/53086/are-regions-an-antipattern-or-code-smell
                        0
                        Допустим, у вас есть некий класс, который заполнен функциональностью.
                        Почему бы не использовать регионы для смыслового разделения кода всего класса на некие фрагменты? Или проще листать девять страниц кода?
                          +2
                          если класс занимает 9 страниц, то он скорее всего нарушает SRP и нуждается в рефакторинге, а не в регионах
                            0
                            SRP очень круто звучит в теории, но на практике с ним происходят некоторые проблемы.
                            Например, у меня есть класс матриц с значительным количеством методов fill. Я, конечно, могу сказать, что так нельзя и нужно вынести филлеры матрицы в отдельный класс (хотя момент достаточно спорный, учитывая, что филлеры используют особенности хранения матрицы).
                            Какой код удобнее читать и писать?
                            Такой:
                            DoubleMatrix a = b.fillRow(1,(i,j)->i+j).fillRow(2,(i,j)->Math.cos(i+j));
                            

                            Или такой:
                            DoubleMatrix a = fillRow(1, (i,j)->i+j, fillRow(2, (i,j)->Math.cos(i+j), b));
                            

                            Учитывая, что вызовов функций заполнения может быть в разы больше.

                            Так же, то как эту проблему в Java 8 со свойстами, при помощи нескольких абстрактных классов и кучи интерфейсов с методами по умолчанию мне жутко не нравится. Если это из-за SRP, то тут он повел себя как антипаттерн, на мой взгляд.
                              0
                              Fluent-запись выглядит более модной, но, если честно, оба варианта вызывают ассоциацию с Haskell.
                              Что именно не так с этим кодом по такому фрагменту сказать сложно и, наверное, лучше не в комментах, но результат вполне может быть началом путёвой статьи для Хабра.
                              Если что, контакты мои в есть профиле. :)
                              0
                              Есть исключения: перегрузка визиторов при разборе большого формального языка.
                                0
                                К сожалению регионы пихают не только в классы на 9 страниц. Вот у автора есть класс на 42 строки (без учёта пустых строк) из которых на регионы приходится 10. Практически четверть кода!
                                0
                                Можно использовать partial классы.
                                  0
                                  Верно, регионы в C# и добавили для «сворачивания» автогенеренного кода WinForms. Потом уже придумали partial классы.
                                0
                                Следует понимать эту строку следующим образом:
                                1. Правильный кодинг стайл включает в себя всё, не только регионы :)
                                2. Использование комментариев и регионов — часть кодинг стайла

                                Т.е. если кому-то не нравятся регионы, можно их и не использовать, но те же комментарии и прочее (правила именования классов, общее оформление) нужно применять и по возможности правильно.

                                Лично мне регионы удобны, т.к. я могу быстро выделить только публичные методы/свойства класса и пробежаться по его «фасаду». Можно было бы обойтись просто группировкой, но с регионами нагляднее.
                                0
                                Здорово!
                                Недавно делал свой «What's color is it?», и для того чтобы картинка всегда оставалась контрастной применял к цифрам инвертированный цвет фона) Можно например сделать так, чтобы глаза меньше уставали)
                                  +4
                                  “What’s” = “What is”

                                  Я бы написал “What color is it?” или “What’s the color?”
                                    +3
                                    What's и is — чтоб наверняка
                                  +1
                                  Я разрешал молотить так по клавиатуре и елозить мышкой прямо на рабочем столе. И когда примерно чуть больше года ему было он славно потролил жену.
                                  Приходит она с кухни — все иконки выставлены горизонтально в ряд по центру рабочего стола. Ну может глюк после выхода из игры, после зумы бывает иконки сдвигаются. Расставила быстро всё по углам и опять ушла на кухню. Приходит через некоторое время — опять та же картина. «Что за хрень?» — расставила всё обратно и опять на кухню. Ребенок всё это время игрался в машинку на столе катал её туда сюда, иногда использовал мышку в качестве второй машинки для компании.
                                  Выглядывает она из-за угла и наблюдает картину, как ребенок мышкой расставляет иконки горизонтально в ряд. 1 год.
                                  Специально не учили. просто разрешали молотосить по клаве и елозить мышкой, везде. залазил на колени, когда играли в зуму, потом он отбирал мышку и весело расстреливал все шары по сторонам.
                                    0
                                    Если есть чужой ноутбук, то всё проще — отдал ребёнку ноут жены и не жалко. Свой нут на растерзание отдавать не хочется. ;)
                                    0
                                    Дети вообще странным образом понимают ценность предметов. То же самое в полтора-два года — нужен был именно мамин телефон, когда подсовывают старый не работающий — выбрасывал. :)
                                      0
                                      Мне думается, что это какой-то «попугайский» инстинкт. Мама тыкает пальцем по телефону и там меняется картинка. Папа тыкает пальцами по клавиатуре и тоже что-то происходит. Если запрещать, то возникнет либо нездоровый интерес (навязчивая идея), либо комплекс. В данном случае я решил разрешить пользоваться компьютером, но под контролем.
                                      Дети чуть постарше ведут себя похожим образом — наши старшие (начальные классы школы) очень заинтересовались написанием игр для брата, будем с ними вместе изучать программирование :)
                                      0
                                      Мельтешат цвета. Запилите плавный переход к цвету, а то тут и до эпилепсии недалеко. Как пример — плавный переход цветов при установке windows 8.1 и выше.
                                      Вот пример. Смотреть с 7:20


                                      Правда там видео немного ускорено. В оригинале немного медленнее.
                                        +1
                                        Идея понятна. Про эпилепсию, конечно, вопрос спорный, припадки возникают если есть предрасположенность, Просто мельтешения цветов мало. В любом случае, её лучше продиагностировать в раннем возрасте (шутка).
                                        Будем «наблюдать за сценариями использования программы», «собирать обратную связь от пользователя», а интересные идеи попробуем воплотить со старшими детьми. Возможно, это выльется в статью про обучение программированию школьников начальных классов :)
                                        Есть несколько идей как развивать игру по мере взросления малыша — учить слова, буквы.
                                          0
                                          У меня ребенок на клавиши жмет очень быстро. Не так как на видео. А при таком режиме цвета действительно мельтешат.

                                          Кстати. Из того что я заметил — самая любимая кнопка — это кнопка выключения. Почему-то все тянутся именно к ней.
                                            0
                                            Полагаю, что причина в основном в том, что кнопка выключения светится :) Я настроил специальный профиль питания, где эта кнопка ничего не делает.

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

                                            Кстати, данная игра, иллюстрирует и правило Парето — 20% мелких функциональных наворотов (мельтешение, задержка звуков при быстром нажатии) займут 80% времени разработки. Вспомнилась статья по теме: russian.joelonsoftware.com/Articles/Craftsmanship.html
                                              0
                                              Думаю что решением данной проблемы будет сопоставление ближайших цветов — рядом стоящим клавишам. Поясню на примере: Клавише «П» поставить в соответствие красный, «Р» — розовый, «О» — фиолетовый, «И», «Т», «Н», «Г» — далее по списку рядом стоящих цветов.
                                              Идея в том, чтобы смягчить переходы при нажатии на ближайшие кнопки, т.к. именно такие переходы и совершаются чаще всего (жми то, что находится под ладошкой :-))
                                              Думаю это не сильно усложнит сам код. Тяжелее будет подбирать такие сочетания цветов. Неплохой идеей будет поговорить со знакомым дизайнером или художником. Уж они то точно должны хорошо разбираться в цветах
                                                0
                                                Можете поучаствовать — ресурсный файл поправить в редакторе :)
                                        0
                                        Супер! Моему правда 1,5 уже поздно, он пристрастился к Игре Клаш оф Кланс, уже умеет собирать элексир и монетки с шахт и строить войска, только пока еще передвигает башни по екрану в случайном порядке и обожает составлять стенки в кучку! :)

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое