If you want to play the end game, It’s available on Desktop only http://tetris.ivaylopavlov.com
Mobile is currently not supported, as it requires a keyboard to play.
The code is available here: GitHub
In my life there’s only one game that has captivated me with its beauty and simplicity and is close to my heart and that’s Tetris. Invented by Alexey Pajitnov in 1984 and turned international phenomenon by the SNES and the little 999 in 1 brick game like the below one:
So one day I decided to embark on a small journey to learn the basics of TypeScript and AngularJS 4 and the more modern Web Technologies, since I left the Web Development space and PHP more than 11 years ago. Originally, I thought I had the scope of the project in hand, how difficult could it be? A matrix with numbers. However, as I was progressing I learned the valuable lessons of You aren’t gonna need it, making everying a separate component, which was partly enforced by Angular’s design and that rotating a Tetrimino in different set of scenarios is not nearly as trivial.
I started with the following simple front-end design:
My design approach was Front-end to Back-end. The UI will shape how the back-end response needs to look like to get to the right outputs. So I started doing online couses on TypeScript and Angular on Udemy. Both by Maximilian Schwarzmüller. The links are here: Understanding TypeScript and Angular 4 – The Complete Guide. Both courses start from the absolute beginning and are very well paced and explained. The TypeScript one I finished quite quickly, as I was already working with TypeScript in my day-to-day job and was using a decent chunk of its features. AngularJS however was a different beast, not saying it was difficult to pick up, but just that it felt quite more extensive and slower to start to use properly. How to use things was easy, but which one to use when was the trickier part. How to bind properly and nest components, when to do a service, etc.
So the backend design, ended on 3 pillar models -> Gameboard (Managing the grid variable), Grid (Managing the current Tetrimino and past applied ones, clearing a line, etc), and Tetrimino offering the basic functionality (Form and Rotation primarily). As you can guess the chunkiest part was the Grid, as it turns out creating a rotation machanism for a tetrimino, that checks for illegal moves is not nearly as streightforward as I assumed at the start. Lesson learned.
The backend was entirely built in TypeScript, using modules, Classes and Decorators, it was quite clean to implement.
The way I implemented it is: The grid is a 2D-Array, where 0 is an empty block, and 1 is a mino block. Later I changed the numbers to 1,2,3,4,5,6 in order to have a color assigned to each tetrimino block. Then each mino was also a 2D array, which I can rotate. Then in the html, pick the image 1.png or 2.png based on the matrix and display. That trick made me feel clever. Saved me a lot of coding of colorful blocks, dealing with dimensions, etc. That’s why the mino blocks are named 1.png, etc in the assets folder.
The difficulty actually came on deciding which moves are legal and illegal, ie tetrimino is blocked on both ends so cannot rotate further, etc. It turned out there are numerous logic for tetris rotation – Atari, Sega, TGM, etc. which I found here. I went for the Sega Rotation, I couldn’t cover every corner case, ie, two diagonal ladder patters and 2 block spaces in the middle, the probability of achieving it is so minimal, that I couldn’t reproduce it in other tetris games, nor even on mine, unless I specifically set up the matrix that way beforehand. So I couldn’t cover every corner case, some would have required a complete rewrite on the application logic and decided not to pollute the code with a bunch of If statements.
Throughout the entire thing, TypeScript, Angular and MDNdocumentation were indespensible.
Well written with plenty of examples. The problem with Google-ing stuff is that the majority of the examples are AngularJS 1, which has almost nothing in common with AngularJS 2/4. So there was no way around for AngularJS 2 without reading its documentation or the Udemy videos, some of which I probably rewatched 6-7 times, especially on the two-way binding.
I went for the classically wrong (some thing it’s the right one) approach of building a working monolith and then breaking it appart. As the Grid component grew and grew, I tried to move logic out of it, failed on some instances, as it meant introducing more complexity of making the Tetrimino class not as compact. The Gameboard class, was literally just the global constants and nothing else.
I couldn’t decide weather to use AngularJS or React in the beginning, but a colleague at work told me he’s worked with both and told me to go for Angular and that was it. No further research. VueJS and other new trendy frameworks, didn’t get a chance. Maybe later down the road for a different project.
This is where the monolith came in handy, when I started to migrate the pure TypeScript code to Angular, which I had broken into the right components, as per the whiteboard sketch above – Root, Left Panel, Right Panel, Grid and separate popups. I dumped the entire code in the Grid component and it worked on first run. Brilliant. The breaking part, however, as expected was tedious. You move something, something else breaks, fix that one, repeat. That one took me a good week and half of after work time. Just to get back to where I started. Also the most annoying one as it felt so much work just to get back to the starting point.
The Data Binding
Angular’s killer feature is hands down the binding, which at the beginning I didn’t truly appreciate. Why did I need to go through all this trouble to display a number from here over there. If you decide build something like this yourself, you end up in “handler hell”, and basically writing a small framework on your own.
I struggled to get the Component Methods called from the HTML at the right time, but every new problem I got, after 2h of reading documentation I fixed and moved to the next one. Now I admit, the design I ended up using is not nessearily the right one, half-way through I realized I should have gone for a creation of a “service”, rather than standard binding, but as it involved another learning curve of Injectors, etc. I stuck with what I already had working. If one day I come back to the project, that’s probably the first thing I’d change. However, I didn’t want the game creation to drag on forever.
I created all assets, except the background image, in Photoshop myself, even uploaded the PSDs for the very keen. Obviously creating pixelart is quite easy. Just doesn’t scale very nicely, but it didn’t have to in my case. I’m a big fan of pixelart, which is why I went for it. I own quite a few posters from eBoy [link here], which kind of inspired me to hop on the new 8-bit game trend. Would this design decision age well? Who knows.
Back to HTML and CSS
When I left Web development in 2006, to watch a video, you had to embed a .flv. Any fancy animation had to be a swf file. CSS was nightmare and IE6 was still ruling the world and had to re-write the page twice.
The original design was much much much better in Chrome, then I switched to Chrome on my Macbook Air, and it was like looking at a Picasso picture, so I had to re-write half the CSS to have it somewhat decently looking in Firefox, Safari on a small screen.
To create the popup boxes and animations, I discovered Angular Material. Plenty of examples on plunkr on the official page, getting it to work locally was elementary. The ease of use of proper animations and professionally looking items is what has brought up quality up in such home-made projects. I would have used the default ones otherwise. I mean really, who has time to customize the styling of a checkbox?
Building & Deploying to AWS
Building was just running “ng build”. Then opened AWS Console, created an S3 bucket, set it access to read public. Select it to host “Static Web Site” and uploaded the created “dist” folder from the “ng build” command along with the images and set the index page to index.html. In Route 53, set tetris.ivaylopavlov.com to the AWS bucket address. Googled and implemented in literally 20 mins.
The lessons I learned
The quality of Open Source has improved dramatically. Since I was a kid, I’ve always been skeptical of Open Source code quality. I’ve missed the turning point while I was in finance and many projects have become really corporate production level. Facebook and Google making their frameworks openly available speeds up web progress which makes everyone a winner, them especially.
Front-end is annoying, I love UI and building simple interfaces that abstract complexity. I will happily do more UI work.
But man, sometimes that cross-browser compatibilty will take the best of me and just have it a bit off for the sake of completion time. Sometimes, you start imagining stuff and the OCD kicks in.
Mobile-support is a completely different beast. The question I had for myself was, how to make it compatible for phones, do I add buttons that emulate keys on the screen? Looks ugly. Nah. How to make the entire CSS scale to a screen less than 800 pixels? Nah. Not now. In the end I decided to keep a good Desktop game, where I compromized on the looks for multiple browser support already. Rather than try to fit all and no platform well in the end. I’ve never done mobile design and development. My approach for mobile was probably wrong from the start. Maybe the next project.
That was a long post. Thanks for reading if you made to this point.
Now here’s a photo of my flatmate and one and only beta tester, Stefan, playing the game on Xbox One.
Maoritzio · 06/06/2019 at 1:07 pm
This is great! thanks! question – after leaning the lesson of YAGNI, if you would have started now from scratch, would you build the entire game with one main component?
Ivaylo Pavlov · 06/06/2019 at 4:35 pm
Even when I was building it, I wanted to write it decoupled, but as I was inexperienced, I was focused on getting things to work vs doing them right. The code quality for my tetris is not good enough. The main component was a consequence of not understanding the framework well enough. Not a good example for good coding practices. Hope this helps!
ailtonbsj · 27/08/2018 at 2:21 pm
Great! I have a Tetris too.
Ivaylo · 27/08/2018 at 3:42 pm
Checked it out! Nice one!