I made a game of Tetris on Google Sheets.
Where can I see it?
Here is a video.
Where to play it?
- Go here
- Make a copy of the Google sheet file and add it to your drive.
- Open it
- Read the instructions
- Press “Start” and play
Do expect some wonkiness — read the bugs part please.
What was the story behind it?
I believe it was Q1 of 2018 when my PM (hey Dennis!) asked me to do this Apps Script project on Google Sheets. If you don’t know, Apps Script is the scripting language for Google apps like Sheets, Docs and Forms etc.
My first thought was to ask him to go and do what he must, to reach satisfaction posteriorly. But then, of course, I was getting paid to be a resource, so I thought if I think myself so hot, how good of a job I can do really?
I have to confess, App Scripts has a great documentation, and thus it is very fun to work with.
This first project was well received, and thus we moved on to a second more involved project. In this second project, we had to have multiple tables in one sheet and be able to insert an arbitrary number of rows and columns in any order in any of the tables without the formatting or the application breaking.
That was my gateway; at that point, I knew this would grow grotesque and I cannot maintain it sanely. Thus I refactored part of project 2 to make it less like an App Script project and more like any other app, with objects, abstractions, and nice methods like
moveDown which would take care of all the references and named ranges for me.
As part of an inner joke and perhaps because of the influence of some libation one Friday evening, I thought to myself if I can make Tetris on App Scripts, and that is how it started. I kept video logs of my progress at first but then I stopped midway. The reason is that I was planning to demo this time lapse to the company on my upcoming second anniversary. Unfortunately, though I was laid off, you can still see those videos in this folder. I like the one where the script won’t stop.
How was the making?
This is a more technical part so you could skip it if not interested.
I started by exploring the deal breakers, like a tile moving down and the ability to move them around. The plan was as follows:
For a single tile (the I):
- Make a colored range drop, a range is a set of contiguous cells on a straight line
- Add controllers for moving them right and left
- Devise stacking of the tiles on top of each others
- Devise the row clearing
Everything went rather okay here:
I put the tile in a three by three bounding box, to help me with moving it down and basically interfacing with the bounding box, instead of every cell of the tile.
- For making a tile fall, I had an infinite loop that will move down the range and sleep for a set time.
- For moving, I would have the code check a cell value and then move the tile to left or right accordingly.
- For stacking, a shadow sheet, called
Board_was writing 1s where a tile rests finally, but not while it is falling. As a result, if the buffer encounters 1s, it must stop. The buffer was the row immediately beneath the bounding box
- Row clearing was fun. I wanted to add a smol animation. Basically check to see if a row sums up to 24, which is the number of columns in the board, and clear that row if it does. Then simply copy and paste what is above to a row lower, which now is empty.
At this point I decided to add all the other tetriminos (or tiles). So I added all the shapes with their rotations on a new sheet called
Tiles so I can copy from there and paste on my board. The three by three bounding box was great here, I could copy the box and paste it into the main board and see the tile appear and start moving down.
But here is where it blew up. The moving three by three bounding box would override the color on the stack below. If this is vague, imagine a stack of tetris tiles nicely fit together, and an “o” tile is coming down, that is the square piece, and it is two by two. Imagine there is room exactly for that “o” piece, i.e. two columns. But the tile has a three by three bounding box, and thus when the piece is entering the opening, so does its bounding box, and so, the extra column is ruining the stacked up tiles as it is moving down, by clearing everything in its trail.
Moreover, the buffer for the bounding box was just a straight line under the bounding box. But for shapes, like the “L”, that is not true when it is rotated. Then the buffer is uneven on the bottom of the tile.
This is VERY obvious now but for some reason, it never occurred to me then: if you treat individual shapes, as three by three squares(a bounding box), you will get the behavior of a three by three square, it does not matter that it does not look like a square.
To fix this, I chose to choose a “hinge” for every shape and every rotation, and copy and paste relative to that hinge. This worked. I chose the hinges as the first upper and then the leftmost one cell in each tile. It was a bit messy because I had to have custom offsets and ranges for each and every one. The same needed to happen to the buffers, now that the uniform bounding box was gone.
The highlight of this was when I realized, that if I put the hinges of every shape, as the nodes on a grid then my data structures will collapse in complexity and everything will be much cleaner. I have never felt this much tangible difference in handling code when one chooses a suitable data structure. The figure below illustrates what I mean by putting hinges on a grid.
After some debugging it was ready. I just had to make the colors cuter.
- The buffer checking is ONLY done for the row below the tiles, and not to the left, right or before a rotation, i.e. rotation near a wall or another tile might break the colors.
- The up next display is not implemented.
Apps Scripts is great. I think it is very underutilized by some people that will really benefit from it. Apart from abstractions to make front-end component, it is possible to extend it, using itself to retain a memory.
I once used a sheet to store and retrieve values from just like a memory. A sheet can be a key value store that one can use to write anything in and wipe.
I sometimes fancy the idea of an MVC framework on top of Apps Script. If the script has no memory this is impossible, but we could save states in another sheet and read from and write to it. I don’t know how slow it would be, but, thought experiment.
Of course, there are those who use it as a database, definitely not better than a SQL database, but I will argue with you that it could be better than MongoDB for you.
Thinking back, I feel pretty arrogant and ashamed to have thought I am above Google Sheets + Apps Scripts; this is a complex system to build and document and its simplicity and intuitive interface does not at all hinder building very complex systems with zero deployments or infrastructure maintenance. The bottlenecking will probably happen if much faster applications are required, but that is not always the case for most use cases.
I might be very delusional here, and people will say: “Well yes of course! I can’t believe you are rediscovering this”, especially that I am aware of communities doing awesome things with it, and especially that I have known people, self-proclaimed non-engineers, making amazing tools with this ecosystem (hi Diego!). Alas, I have never come across an application engineer, in my 2 years in grad school and two years in the industry that has told me, or even alluded to, how powerful Apps Scripts can be for most use cases.
Trust me, give it a try.