Falling Blocks is great! I used to play it at the pub back in the day. Yea, its old. I know. But what the heck? This project took me one week from start to finish - and I only worked on it a few hours each day. Things went fairly well. No major complications ... just : code, debug, test, fix and repeat until I was done.
I had never done anything 'electronic'-cky since my second year of Computer Engineering over 23 years ago until I ordered an Arduino Starter-Kit late last fall and eagerly anticipated its arrival. I didn't even know what I had ordered, not realizing Arduino was a MicroController, I thought I was going to get a bunch of transistors, diodes and LEDs. But lo and behold, there it was, right there in the broken plastic box I got in the mail along with all the other widgets that pale in comparison to the Arduino Uno.
I took an on-line beginner's class with Udemy and sparks flew ... literally.
I've bought a lot of on-line Arduino 'kindling' since then. It took a few agonizing months for slow boats from China to eventually get here. And then there was a Canadian Pacific railway blockade that further impeded my tiny nuggets of joy from reaching their happy home before we all got hit with the Corona Virus. So, I went to my local electronics dealer here in Moncton, New Brunswick, Canada, but he charges 5x the price I pay on-line. and I am way too cheap for that. So mostly I just sit and wait for the next mail delivery and my Arduino bits do eventually get here. Despite some frustrating instances after I had completed the on-line course, watched a few instructional YouTube videos and read several Arduino Language Reference pages, and I was sure I knew what I was doing, I still got smoking sparks where buzzers and gyroscopic LEDs should have been... but I persisted and eventually built a few simple projects.
Things are better now. I finally have a few essentials like a regulated power-supply, a reliable multi-meter and a decent soldering iron. So I'm all set.
There are endless possibilities when you have an Arduino, or two or three. I've built a robotic arm which was a lot harder to build the moving parts for than it was to code but turned out to be no fun at all because those SG-90 Micro-Servo motors are way too weak to do anything with but I went out and bought the bigger, better, badder MG995s so that'll rock soon enough. There was an LCD1604 'Runner' game where you ran around in a maze shooting bad-guys, a Simon-Says and little else besides as I've been busy with C# projects and, most recently, and actual Art Project which was taking up way too much of my time. So, last week, a brand new Max7219 4x daisy-chained dot-matrix (8x8x4) arrived in the mail and it was reason enough to put down my art project for a few hours a day during the evening to play with my Arduino.
The first night, I defined the (because of copyright issues with a certain famous video game classic from Russia, I will decline from using this project's working title for this article) Falling Blocks pieces as a class which could rotate and calculate where each of their squares were as they fell and pieces began dropping on my dot-matrix game screen. The next night, they were spinning and moving left/right with my breadboard 74HC165 load-register'ed push-buttons. On the third night, the map data was born and pieces now obeyed the laws of physics resting where they lay. Rows were being erased and funky things happened when they fell, but I fixed all that. Even though there were some things which I did have to rewrite, the whole thing only took me exactly one week. The rest of that week was spent on polish. Besides an afternoon with the soldering iron, there were a few words of Intro, scrolling-text, scrolling-text that didn't gobble up all my memory, and finally scrolling text that went sidewards in bitwise what-words way. Then high-scores and the discovery of a magical thing called the EEPROM,
"like a tiny hard drive", Arduino Reference.
Its pretty cool! My coding background helped with all the bitwise operations involved in dropping odd-shaped tiles through 4x8byte arrays of dot-matrix screens, and what little electronics basics I walked away from University with was enough to get'er done.
Now, unfortunately, I'll need a support-group to help wean me off play the game of Falling Blocks ... but I hear they have donuts and coffee. So that's alright.
This project is a cheap inferior copy of a world classic called Tetris.
As per Wikipedia, the original game of Tetris was designed by Alexey Pajitnov and Vadim Gerasimov who first released the game in Russia in 1984. After years of disputes over rights for their creation, Alexey Pajitnov wrestled those rights from Nintendo in 1996 and founded The Tetris Company. Over 202 million copies of the game have been sold consisting of approximately 70 million physical units and 132 million paid downloads. The game holds the Guinness World Record for Most Ported Title and is available on 65 platforms, 66 if you include my Arduino Nano!
- Arduino Nano (or any controller you have) with matching USB connector
- 4 daisy chained Max7219's
- 74HC165 load register (optional)
- 6 push buttons
- 5V DC buzzer
- 5k ohm potentiometer
- wire, solder & soldering iron, tweezers, pliers, wire-strippers
- tupperware, soup-jar lid and chop-stick are optional
Watch a 4 minute video showing the end result.
The game of Falling Blocks is generally played with a joystick and two buttons but since the joystick I got in the mail with the Arduino Starter Kit has taken some damage after only a little use, I opted for 6 push buttons (which I happened to have salvaged from old scrap electronics a few months ago) instead. Since there's only six buttons in this project, I managed to save 2 output pins at the cost of one 74HC165 Load Register, 6x 180 ohm resistors and a bit of solder but I stand by this difficult design decision as it gave me a reason to play around with my soldering iron. And who doesn't love to play around with their soldering iron?
The 74HC165 Load Register is a micro-chip that can read eight inputs and communicate the states of those inputs to your microcontroller serially using only four lines. You can daisy-chain them and read 16, 24, 32 or more inputs at once still using only four digital Arduino pins (the Nano only has 13 which means for some projects a Load Register is essential). Using one for this project was a bit of an overkill but I have a dozen on my shelf anyway and the buttons are soldered onto the same removeable board as the 74HC165 and can be unplugged and reused for a different project whenever I get bored with Falling Blocks (or the chop-stick holding it together finally breaks). The a diagram below shows how to connect an Arduino Uno to 8 Dip Switches using a 74HC165.
In this project, instead of dip switches, I used 6 push-buttons and tied the 74HC165 to the Arduino using female connectors. The push-buttons and pull-down resistors had to be soldered together with the 74HC165. This was the only soldering that was required for this project aside from the buzzer and potentiometer. The only thing of note with regards to the Load Register in this project is that pins 5 & 6 (D6 & D7 respectively) are both tied to ground since there are only 6 (of a possible 8) buttons connected to it, those two lines are wasted and better left not floating. Also, since the Arduino Nano has only two ground pins and there are three wires needing to be grounded (74HC165 Load Register, Max7219 Dot Matrix and the buzzer) I had to create a 1:3 wire splitter with old jumper-wire ends so that I could still use jumper wires to connect these peripherals together without solder and provide them all with a common ground.
The schematic below shows how the Falling Blocks components are connected to the Arduino Nano.
During game-play, the user-interface is quite simple and intuitive. The four buttons on the left are directional buttons and the two on the right control the falling piece's rotation.
However, since the Up button has no use in the game, it is available as a control button for two-button combinations.
- Up + Rotate Right = Pause
- Up + Rotate Left = preview next piece
- Up + Right = toggle Music
I at first implemented these button combinations and let the individual buttons still control the falling game piece which made playing the game difficult if you rotated the piece trying to see what would fall next or slid a piece over trying to toggle the music. So I recalled some distant lecture about Finite State Machines and went all FSM on this project. I ended up making FSMs for all three of these combinations when all I really needed to do was disable all controls while the 'up' button is down but at the time I hadn't settled on the up button being a control button at all but was still using the following two-button combinations:
- Left + RIght = pause
- Up + Down = toggle music
- Rotate left + Rotate right = see next piece
So I'll tell you about my Finite State Machines as they work adequately even though they are completely unnecessary and by far over complicated in avoiding game-time control issues. You can look at the original code with these three FSMs (Pause, Music & ShowNextPiece) by downloading the zip file. But for the final project, I got rid of all three of these and shunted game-controls while the 'up' button is pressed. bada-bing, bada-boom... much easier.
UI Finite State Machines
A finite state machine is a way to define different states in which a given automated system is in. Once you know all the possible conditions your program can be in, you can then create a path from one state to the next connecting them all together so that they will transition from one state to the next given the conditions of your data, sensors or user inputs. Here's a simple example:
FSMMusicOn = 0,
The Finite State Machine for toggle the music defines the 8 unique states shown above. When the power is turned on, the variable
eFSMMusic is instantiated and initialized to zero (
FSMMusicOn). The function
Music_UI() before it plays a note and the
Music_UI() uses a
switch-case for the
eFSMMusic variable and decides what to do. While it's still in the
FSMMusicOn state, it tests the condition of buttons up and Right. If both these buttons are pressed, then it changes its state to
FSMMusicStartUpRight until it cycles through again and switches to that state's case and tests those same buttons again eventually falling into the
FSMMusicOFF state when both those buttons are released by the player. All the while, the
PlaySong() will only play music if the variable
eFSMMusic is in the state
Finite state machines are very powerful tools that have their uses and can greatly simplify otherwise difficult program flow issues but they were wholly unnecessary here. Deciding to use the one 'Up' button as a control and shunting all other game-related button controls whenever that one button is depressed only required three boolean variables to define the conditions of all three options (pause, music & see-next-piece).
"Simpler, when all things are equal, is better," a wise man said.
(or it might have been Tyron Lannister... I don't remember)
There are seven different kinds of pieces in the game of Falling Blocks. In this project, I coded them using a class called
classFalling BlocksPiece that keeps track of where the piece is on the screen as it falls. Each falling Falling Blocks piece is made up of an array of four instances of the
class classFalling BlocksPiece
byte bytPieceType = 0;
classFalling BlocksPiece_Square squares;
void setType(byte bytTypeNew);
The Pieces can rotate about themselves and the 0th indexed square is the pivot-square about which each piece rotates. In the image below, you can see how each of the pieces is composed of those four squares and how those squares are configured to define the piece's shape.
classFalling BlocksPiece_Square keeps track of two Cartesian points. The first tracks where it is located relative to the pivot-square and the second positions it on the game-map. The pieces themselves don't need to know what shape they are but only where their squares are located relative to the pivot-square so when they need to be rotated, each square changes its position relative to the pivot square essentially changing their shape but effectively fooling the player into believing that the piece has been rotated. Potato ... Tomato. Whatever you call it, it looks like its rotating.
void classFalling BlocksPiece_Square::RotateRight()
int intY_Temp = pt.X;
pt.X = - pt.Y;
pt.Y = intY_Temp;
void classFalling BlocksPiece_Square::RotateLeft()
int intY_Temp = -pt.X;
pt.X = pt.Y;
pt.Y = intY_Temp;
The piece's location itself is recorded in the
classFalling BlocksPiece and determines the pivot-square's location which sets all the locations of the three other squares that are positioned relative to it.
Although the game can go on for some time and a hundred pieces or more may fall before the titles roll when the game ends, there are really only three pieces in the game. All three pieces are in a single array.
- Falling piece
- Next piece
- Test piece
The Falling Piece is the piece you see on the screen which the player controls coming down. The Next Piece is the one flashing at the top of the screen waiting to come down. There are two pointers to these two pieces and the pointers alternate between the two every time the Falling Piece stops moving. The Test Piece is a clone of the Falling piece used in collision detection to determine if the Falling Piece can perform an action the player intended.
Of course, you need to test whether or not a piece can fall, shift sideways or even rotate. To do this, the Falling Piece's variables are copied onto the Test Piece. The Test Piece is then manipulated according to the player's intentions and if the resultant rotation and location of the Test Piece does not conflict with the current state of the game map (more on that later), then that move is allowed and the Test Piece's altered values are assigned to the Falling Piece and the game goes on. Sometimes a rotation will lead to a collision in which case the Test Piece is moved Left or Right and another collision test is made. If the Test Piece's final state here is permissible, then the Falling Piece is not only rotated (as per the Player's intention) but is also shifted-over by one square to allow the rotation to proceed without causing a collision with the game's map. At the time of writing, the rotation collision is only altered by a single left or right shift and not a second or third which, in some cases, means that pieces won't rotate if doing so would require that they be shifted more than once in order to avoid a collision. This could be corrected, I just haven't done it.
The moves and rotations, along with their requisite collision tests are done outside the
classFalling BlocksPiece as those operations require access to the Map Data.
When the Falling Piece can no longer move, it becomes stationary. In that case, it is no longer necessary to keep track of it as an integral Falling Blocks piece made up of four squares. Instead of remembering what fallen squares belong to which piece and creating new pieces that will only be broken apart and disappear as the Player clears row after row, the Falling Piece's location is used to set the values in the game's Map Data. Since the game screen is made up of 4 8x8 dot matrix arrays, there are 8x32 squares on the screen. Each square can either be occupied by a Falling Blocks-Piece-Square or not. The Max7219 daisy chain is intended to be viewed sideways. In this Falling Blocks project, however, it is configured such that the 0th Matrix is at the top of the game screen while the 3rd Matrix is at the bottom. Each Matrix consists of 8 bytes (normally on top of each other) with the 0th byte at the top and the 7th at the bottom (when viewed sideways as in the diagram below). To draw anything on the screen, you have to address the Matrix and row of your Max7219 daisy chain (which, again, is normally sideways). The diagram below shows how these Matrices are set-up.
The game's Map Data consists of 8 unsigned long integers that make up the columns of the game. When shown horizontally as they appear in the diagram above, you will notice that the bit indices of these unsigned long integers start at the top of the game-screen (right of the image) and end at the bottom of the game screen (left of the image) with the unsigned long integers themselves indexed in the same order that the Max7219's own bytes are with 0 at the left of the screen (top of image) and 7 at the right (bottom in image). When a piece falls, the data is altered and the bits are set where the Falling Piece's squares are before the next piece starts to fall from the top. When it comes time to draw the screen, the Map Data is copied onto a temporary array of 8 unsigned long integers and the Falling Piece is added to them. Then each temp unsigned long integer is shifted 8 bits to the right and has its Least Significant Byte used in the
lc.setRows(intDisplayCounter, intColumnCounter, bytDisplay);
call as the
bytDisplay to draw on the screen if any of those 8 bits have changed since the last time the screen was drawn. Two variables are used to keep track of which of the 8 bytes of each of the 4 Max7219 Matrices need to be updated.
unsigned long ulngScreen_RowsNeedRefresh = -1;
byte bytScreen_ColumnsNeedRefresh = B11111111;
The unsigned long
ulngScreen_RowsNeedRefresh keeps track of which rows have been altered while the
bytScreen_ColumnsNeedRefresh keeps track of which columns were altered. When the screen is refreshed, these two variables are reset to the default values (all bits set high) and they remain that way until the Falling piece is moved or rotated at which time all the rows and columns the Falling Piece occupied before and after this change are set in the '
Refresh' variables above to be sampled the next time the
drawScreen() function is called.
As game play continues, the rows that are filled flash then are erased, and the rows above those that were erased fall to replace them. For this to happen, the Falling Piece falls until it can fall no further. Here, the algorithm needs to test which rows are completed. It could test all 32 rows but why do that when it knows that the Falling Piece is the only piece that could fill a row. So it tests only those rows where the falling piece ended using the
ulngScreen_RowsNeedRefresh variable mentioned above. It creates another unsigned long integer and sets the bits that correspond to the rows that the player filled in the game. This unsigned long integer is then used as a mask in bitwise operations which set or reset the bits in the Map Data before it is drawn to the screen to create the flashing vanishing rows effect. After which they are erased one row at a time by shifting all the bits above that row to the left (left here being a bitwise left which corresponds to 'down' on the screen) for all columns for all vanishing rows starting from the one furthest down the bottom of the game screen. The screen is drawn again only once all the vanishing rows have been erased in this fashion.
The Max7219 is well documented and there are plenty of tutorials available on-line to provide you with all the functions you need to make text scroll on your dot-matrix.
So I wrote my own.
Just stupid or stubborn, I guess, but I like to code and it only took me a couple of hours to implement a simple sideways scrolling text function with all upper/lower case and several other characters besides.
Watch this video I posted on FaceBook.
That works fine if the only thing you want your project to do is scroll text sideways but that Text-scrolling project defined each character's bit-map using 8 bytes of memory which added up to over 82x8 bytes for a total of 656 bytes just to define those 82 characters. That may not sound like much, but remember that the Arduino Nano has very little memory and no more to spare. With any other project, you may run into Memory issues. As memory in most micro-controllers is at a premium, you have to optimize memory usage or pick and choose what you want to do and drop those things that require too much memory.
Here's the memory usage for the Scrolling Text project (that does nothing but scroll one sentence of text)
compared to the memory usage of this Falling Blocks project (that's a game of Falling Blocks with scrolling text besides)
which is not only limited to the 26 upper case letters, space and digits 0-9, but condenses the way these characters are defined. The Falling Blocks project defines each scrolling character's bit-map sideways in a one dimensional array of bytes.
String strCharList = "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789";
String strChrWidth = "4444444435445544544545555545344544444";
Have a look at the image below which shows the same data rotated sideways and you can better see the character bit-maps.
There are no spaces between the letters in their bitmap definitions and each of the first four letters of the capitalized alphabet seen above use only 4 bytes as opposed to the 8 bytes the first implementation required.
Whenever a character bit-map is needed, that character is searched in the
string and its index in that
string is used to sum all the integer values in the
string that matches
strCharList in length and tracks of each of the characters's bitmap widths. This way, summing up all the integer values of the widths of all the characters that precede the one you want to draw gives you the starting index of the character's byte in the
bytFont array and that same character's width value is then added to its start index to determine its end index. With these two indices the bitmap is read from the
bytFont array and an 8 byte array which centers the character is created and returned to the calling function and all characters are drawn using an algorithm similar to the one that was implemented for the Scrolling text project mentioned above.
Finite State Machines Do Have a Purpose
The scrolling text project does only one thing: infinitely scroll one string of text. But the game of Falling Blocks has several different things to say depending on where it is in the game. At the start of the program, it scrolls the string "
Falling Blocks 2020 HIGH SCORE <high score> <name high score>" upwards in an infinite loop until the player presses the
RotateRight button which launches the game. At which point, the scrolling stops and the game proceeds until the player either presses the Pause button-combination, or the game ends. Pause mode scrolls a different string vertically just as the intro text did but at game end, things get a little more complicated. Originally, the Game Over text was also scrolling vertically but I found that too slow and it took too long to let the player know what his final score was. So I implemented a scroll-sideways function and let the two four-letter words GAME & OVER scroll in from the right. The letters are all still vertical as before but the first word GAME enters the screen one bitmap column at a time from the right until the word is centered horizontally at which point it waits briefly before it repeats this operation again for the second word OVER and waits again before telling the player his score in similar fashion. Here, it must decide whether the player has beaten the high score or not. If there is a new high score, then the words HIGH & SCRE scroll in and the UI lets the user enter his name. Once the UI is complete and the high scorer's name has been entered, it is stored on the EEPROM (more on this below) and the display continues to cycle through GAME + OVER + <score> + HIGH + SCORE + <high score> + <high score name>, it normally does when the player does not beat the high score.
You can see that
else and embedded
what else's would make a spaghetti like mess of this, so an FSM was used to make it all happen.
Here is a list of that FSM's possible states:
Since FSMs were explained earlier in this article, this particular FSM should not seem overly difficult to understand. Those states with the word '
Init' at the end of their names are states where the text to follow is defined and made ready to scroll onto the screen. The next state after each '
Init' state is the state that scrolls in a long string of text vertically or any given 4-character string horizontally from the right. '
Delay' means the screen stays static for a second before moving on to the next FSM state. And the exception to all that are the two '
_UI' states that side step any scrolling to let the high-scorer enter his name then wait until the Rotate-Right button (used by the player to complete the operation) is released lest it be mistaken for the player's intention to start a new game.
UI: Entering the High Score Name
When the Scrolling Text FSM falls into
FSMScrollingText_HighScore_PlayerName_UI state because the player has recorded a high score, the program flows into a function that interacts with the user until he has pressed the Rotate-Right button which signifies he has finished entering his name. This function clears the screen and resets the
strHighScorePlayerName string to "
AAAA" and creates an array of four characters to keep track of each letter in the player's name. Using an integer variable to remember which character is being edited (corresponds to which Matrix Array is flashing), it tests the directional buttons states and either scrolls through the letters when either the Left or Right buttons are pressed or cycles up and down through each of the four Dot-Matrix displays to allow the player to change a different letter. When the player is done, he can press the Rotate-Right button and the information is saved.
Keeping Track of High Scores
The Arduino microcontrollers all have some EEPROM memory. This memory is saved even when the power is cut off and can be used to initialize or reboot your project whenever you power it back to life. In the case of the Falling Blocks project, I found the EEPROM useful for storing the High score values. I could use it to store those 160 odd bytes of Character bitmaps but I haven't bothered. Because EEPROM memory has a life expectancy of 100 000 writes (read does not deteriorate your EEPROM) its best not to use the EEPROM memory for game play variables or any other type of variable that is altered several times a day or more to keep your EEPROM from deteriorating and becoming useless. They suggest, when you do write to an EEPROM, to first test the value of the byte you're about to write over and see if it's the same as the value you're about to over-write it with, because if it is, you can extend your EEPROM's life by not over-writing a byte of memory unnecessarily. The best way to do that is to avoid using the
write() function altogether and use the
update() function instead since it will only write to the intended address if the old value is not the same as the new value you're intending to replace it with. So using the EEPROM memory to store, say, bit-maps for scrolling characters would be an efficient use of the EEPROM and I would have come around to doing that if there were a need but as the project compiles with 49% of dynamic memory remaining there is no chance of it ever having any shortage of memory.
update() functions are straight forward but I had some trouble saving the unsigned integer value of the high score because I hadn't clued into the fact that you can only save one byte at a time, which in the case of the unsigned integer took two operations instead of the one I had imagined.
As you can see in the video I posted on YouTube, the build for this project required a bit of rifling through the recycling bin. First, I cut and straightened an old clothes-hanger, then bent it to shape and covered it with electrical tape. I used galvanized steel wire to tie the button controller to the bottom of the bent clothes hanger and assured myself that none of the exposed steel made any contact with the button wiring. Then I used two plastic zip-ties, cut the ends off to make them flat and duct-taped them to the top of the lid, being certain they were even with the shape of the bent clothes hanger that they would help secure later. Here, I measured the lid next to the bent clothes hanger where the button controller should go and used a butane lighter to heat up a sharp scalpel and neatly cut the plastic tupperware lid. At this point I cut eight holes in the lid on either side of both taped zip-ties so that I could run undamaged zip-ties through the holes and secure the clothes hanger to the under side of the lid, snugly fitting it along the edge that I cut out for it. The cut zip-ties duct-taped to the top of the lid are there to make sure that the zip-ties going through the holes and around the clothes-hanger don't tear the plastic tupperware. Two zip-ties later and the Arduino Nano was held fast to the clothes hanger. I cut a hole for the potentiometer, slipped it through and screwed it tight then used the point of the hot scalpel to sear a series of small holes overtop where the buzzer is glued to the underside. I had to also cut the bottom half of the tupperware to fit it onto the lid then measured where the USB wire sticks out of the micro-controller and cut a hole for it through the bottom of the tupperware there.
When I tried using the game like this there was little to hold on to while playing so I punched holes in the lid of a Ragu spaghetti sauce lid and tied it to the clothes hanger so it sticks out beneath the button controller. That still didn't really help, so I punched two more holes at the very back of the Ragu lid and tied a chop-stick across it so the base of my palms press down on it while my fingers hold up the controller which I control using my thumbs. Not pretty, but it works.
And that's my project. I have some rechargeable batteries and the bits needed to tie them to my Arduino Nano so I can plays sans-USBilical cord, but that's all still on a slow-boat from somewhere ... so maybe in a month, or two or three, I'll update you on that. And maybe, I'll get around to letting the pieces shift two bits or more before I'm done.
Until then, happy Quaranduino-ing!
- 18th August, 2020: Originally published
Christ Kennedy grew up in the suburbs of Montreal and is a bilingual Quebecois with a bachelor’s degree in computer engineering from McGill University. He is unemployable and currently living in Moncton, N.B. writing his next novel.