A game for the little ones is a simple idea that is not a shame to include in the resume
Prehistory
My son, like all the children of programmers, probably got his first keyboard when he couldn’t sit. Now he is a little less than a year old, but he already understands the difference between the "toy" and the "real" (Daddy's) keyboard - if you pound the buttons of the present, the picture changes on the screen, and the computer sometimes makes some sounds.
Since I don’t want to lose all my data, the child is sometimes allowed to click on the buttons of a locked computer. Unfortunately, for a child it is not very fun, because the computer has only two modes (two pictures) - the password entry screen and the actual lock screen. ')
To make the process of mastering a computer more interesting for a baby, I decided to write him a simple game. Being an experienced programmer, it was decided to build the whole process “correctly”.
Requirements
The customer (my son, age <1 year), like all normal customers, found it difficult to write in writing the consistent and complete requirements for the product, so I had to help write myself.
Functional:
The application works in full screen mode.
You can click on everything, but the most accessible methods of exit or program switching should be blocked.
Visual feedback - the background color changes when you press, the pressed symbol is displayed in the center of the screen.
Sound feedback - the application makes a sound when you press a key.
Predictable behavior - the background color, symbol and sound must always be the same for the same key.
Not functional:
I should not be ashamed of the written code.
The code should be valuable in itself.
The architecture and all solutions must be “correct” - as in a custom project.
In addition, it was decided to use a flexible, iterative approach to development with small development cycles resulting in receiving feedback (SCRUM).
C # and Visual Studio were chosen as the programming language and development environment, as they provided the performer with the highest speed.
Implementation
From one of the old projects, the code was extracted to create an application deployed in full screen:
Further, the MouseKeyHook library was found in the wilds of the Internet, with examples of how to block the Windows button. Similarly, the examples were blocked by Alt-Tab and Ctrl-Esc. Now you can exit the application only by Alt-F4.
Next, code was written that initializes the random background color for the pressed key:
Used new random (seed), so that at each launch random gave the same values.
To make the colors more or less meaningful, I randomly selected a value from the KnownColor enumeration, which was then converted to Color and assigned to Form.BackColor.
Alphabetic characters and numbers were supported.
The symbol was output “as is” - the Q key could display “Q”, “q”, “D”, “D”, depending on the active input language and the state of CapsLock.
The first alpha tests on themselves revealed the following implementation flaws:
Form.BackColor strongly disagrees to accept the color Transparent.
Black is accepted, but the symbol is not visible on it.
There are a number of keys that can be pressed, they have a symbol, but they are not processed by the program or do not display the symbol - Enter, Tab, Space, the block of numbers above the letters and the block of numeric keys on the right of the keyboard.
I really didn’t like the KeyDown / KeyPress processing code - it was necessary to select the ranges of characters 'AZ' and '0-9', space, Enter. There are many not very intelligible blocks of conditions and a complex code for calculating the size of an array of random colors and sampling colors from it.
In the second iteration, the following changes were made:
A simple WinForm utility is written that just “listens” to clicks, saves them to the Symbol-Key dictionary. This allowed to solve the problem of the output of Russian / English letters.
The utility has a save button to the file.
Since the Space and Enter keys in this case triggered the button handler, and Tab caused the button to go, even if it was not selected, we had to handle these cases separately - set TabStop = false for the buttons and insert ActiveControl = null wherever possible.
The utility helped to identify all significant keys - it remembered the key when KeyDown, but added it to the dictionary only by KeyPress, respectively, everything that does not have a symbolic representation (Alt. Shift, Ctrl, Windows, function keys) was ignored.
Key processing in the game itself can be greatly simplified to a dictionary search.
The file format was the simplest - ready sets are separated by a line break, and the fields (Key-Symbol-Color) in the set are separated by \ 0 (space, tab, and characters like a comma could not be used, since they could be part of the set)
After saving, invisible characters were manually replaced with Unicode characters missing on the keyboard.
The color was not chosen randomly, but was taken sequentially from enum KnownColor, starting from the next after KnownColor.Black (KnownColor.Transparent comes a little earlier).
Alpha testing on itself was quite successful and a demonstration was held to the customer.
The customer showed interest in the product, allocated 2 minutes for testing, evaluated the work as a whole positively and pointed out the following disadvantages:
Insufficient audio feedback (only the PrintScreen key emits sound).
A small luminous button in the far right corner of the laptop (the screen goes out) is incorrectly processed.
Inspired by the support of the customer, the author’s team conducted a retrospective and made the following conclusions:
You need to use an external keyboard without power management buttons or mask the hardware button with your hand.
It's time to move on to sound feedback.
For audio feedback, it was decided to make sounds that correspond to notes (piano keys). A quick search on the Internet made it possible to find a formula for calculating the frequency of sound for each key, and this formula was promptly implemented in the C # code. Console.Beep is used for direct sound output to the speakers (and that works the same!). The first run showed flaws:
The author inattentively read MSDN, namely the string “ranging from 37 to 32767 hertz”.
Low sounds up to about 110 Hz sound disgusting and can not be shown to the customer.
A sound duration of 300 ms is too long.
The sound is output synchronously and causes a delay in drawing the background.
Based on the results, the following changes were made:
To form frequencies from 110 Hz (the 25th piano key, A2).
The duration of the sound is 100ms.
Output sound in a separate stream.
The team expressed the suspicion that it is necessary to do Lock in the second thread for the execution time of Console.Beep. Subsequently, the suspicion was not confirmed, but it was too lazy to remove the lock for didactic purposes.
Use double buffer when changing colors so that there are no bands on the screen when you quickly press the keys.
This version was highly appreciated by the team, and since there was some time left for the customer to demo, the team decided to refactor:
Implement the MVC pattern, select the game logic in the controller, in View, leave only the code specific for working with the form (switch to full screen, event handlers).
Cover the controller with unit tests
Extract the dictionary file with triples “Key-Symbol-Color” to resources and implement the Russian and English versions.
Since on the working laptop (and we planned to hold a demo on it), I have an English locale, the locale setting through the config was implemented. In this case, its own section was added to the config and a simple file was implemented for accessing this section, which returns the typed values of the config variables.
Total
As a result, we got a funny toy for a child, which develops fine motor skills and memory (for favorite colors and notes), as well as code that can be used to demonstrate the implementation of the “right” approaches. For example, for students or junior-developers.
Here is a list of what you can learn by toy code:
Work with WinForms (full screen, double buffer, keyboard event handling)
Work with localized resources.
Use MVC pattern for WinForms (yes, it’s not necessary to switch to WPF for this).
Use of the Singletone pattern (multi-threaded).
Working with Moq when developing unit tests.
Work with Shouldly when developing unit tests.
Parsing lines / files.
Multithreading and blocking threads.
Working with a config file and creating your own sections.
Correct coding style and use of comments and regions.
Work with the debug console (logging events).
Enumerating enum values with Enum.GetValues.
Working with static Array methods (Copy, IndexOf).
Work with unmanaged objects (using).
“Responsive” work of the form - confirmation of the exit, using the save file dialog.
Working with NuGet and pumping out packages at build.