So this is Symonator, my homage to the classic Simon repeating game. You’ll notice there are 16 buttons. This leads to an interesting question: How does one quickly read the status of 16 buttons and compare it to a required pattern, especially when that pattern may require more than one button to be pushed at a single point in time?
Note that for this discussion, I’m assuming you’re familiar with Arduino and how to read the status of an input pin via digitalRead. Although I’m using the Arduino IDE for the ATMega2560 that drives this game, I use the port register directly to read the status of the pins. For the Symonator, the buttons are all connected to PORTA and PORTC. A is used for buttons 1-8 with C being connected to 9-16.
By reading the byte value associated with the port, I can get the status of all 8 buttons at once. The input pins are all set with their internal pull-up resistors on, and the buttons pull those inputs to ground. So, if no buttons for PORTA are pressed, the byte value of the port will be:
PINA = 11111111
If the 2nd button is pressed, the byte value of the port would be:
PINA = 11111101
We can do this because PINA is a reserved word that returns the status of the 8 inputs pins for that port in the form of byte. Each pin is a bit in that byte, with 1 being high and 0 being low. Thus, all pins being high would yield a byte value of 255. All pins being low would return 0. The 2nd pin (bit 1) being low would return 253 (11111101). Remember that bits are numbered starting with 0, i.e. for 8 buttons, we have bits 0 – 7.
So, how do we compare this to a required pattern? Well, first, the pattern is randomly generated during game preparation. It’s held in an array of bytes. If a button is required to be pressed at a particular point in the array, its bit value for that byte is set to 0. So, for example, if button 4 (bit 3) is required, the byte is 11111011 (byte = 251). I used two arrays of bytes to keep track of this (one for buttons 1-8, another for 9-16), but for purposes of discussion, let’s just look at one.
Let’s suppose that for a certain point in the pattern the player is supposed to repeat, the player is required to press the 3rd button (2nd bit). The required byte would look like:
required_byte = 11111011;
Remember, since the buttons pull the pins low, we see a 0 when the button is pressed. Now, we can obviously check for a correct button push by comparing the port byte for the buttons to the required pattern:
if (required_byte == PINA) { correct_flag = true;}
Now, let’s compare these two by looking at the bits of the bytes:
PINA 11111011 (3rd button pressed)
required_byte 11111011 (3rd button required)
These obviously match up, so the player pressed the correct button. If, instead, we had:
PINA 11111101 (2nd button pressed)
required_byte 11111011 (3rd button required)
This would not match up, and the player pressed the wrong button.
So, by simply reading two port registers (after some debouncing), I can quickly determine the status of all buttons and compare that to a required pattern.
But what if two or more buttons are required for multiple players? What if only one of the correct buttons has been pressed? I can’t compare the bytes because they would not be equal…..but how do I determine if only one of the two or more correct buttons has been pressed? I want to accept “partially” correct patterns until the full pattern is provided with the correct button push while not accepting an incorrect button. To do this, I use some bitwise operations and logic.
First, let’s assume the required pattern is:
required_byte = 11110110 (the correct pattern requires the 1st and 4th button)
And let’s suppose only button #1 has been pressed. This means that the port byte is:
PINA = 11111110
To check this but not fail the test, we first AND the required pattern and PINA:
PINA 11111110
required_byte 11110110
AND of both 11110110
Note that ANDing PINA and the required pattern yields…..the required pattern. Any unpressed “correct” button does not penalize the player (as long as the player hasn’t run out of time). As long as the required pattern is equal to the result of AND of both the port byte and the required pattern, play continues. In code, it looks something like:
if ((PINA & required_byte) == required_byte) { // keep checking for proper pattern }
But what if a wrong button was pushed? This will catch that:
PINA 11011110 (buttons 1 and 6 pressed)
required-byte 11110110 (buttons 1 and 4 required)
AND of both 11010110 (not equal to the required_byte)
As you can see, the AND result is NOT equal to required_byte. A wrong button has been pushed.
This works for multiple ports as long as I keep the pattern order sorted out. Reading ports and doing logic like this is very fast in Arduino compared to individually reading all the pins and doing a step-by-step comparison. It also permits cleaner, simpler code. (And my code is ugly enough to begin with.)
So there you have it. How to quickly compare 16 button positions to a required pattern while playing Symonator.