Tetris
Front-end practice, part 1.
Introduction
First, thanks for reading. I decided to write this blog because for the first time in a long time I’m not really working for anybody and I figured it would help to organize my thoughts as I spend time picking up new skills and pursuing my own ideas.
Second, just to warn you in advance, I’m just writing about stuff because I happen to be doing it, not because I think it’s some earth-shattering contribution to the field of software engineering. In fact, since I’ll be posting about stuff I’m trying to improve on, I’ll probably be mostly writing about things I kinda suck at and/or don’t know that well.
Right now, I’m focusing on front-end development because I’ve spent most of the last ten years aggressively avoiding it and letting more experienced colleagues at my last two jobs do it while I focused on web services and business logic. So I’m doing a lot of little projects to get up to speed on things. My current one is to implement some simple games in Javascript.
I started off with Tetris - it’s an easy, well-understood game, and I should be able to write an implementation in under a day, even with a totally new set of tools.
Plain Javascript
My first implementation uses no frameworks whatsoever, and was initially a single, self-contained page. It handles all of the standard logic, including scores and ending the game. The actual board is rendered as a literal table of cells of various colors (or black, to match the background, for empty cells).
This implementation is pretty straightforward, but I’m not sure I learned a ton from it, other than reminding me of the names of some properties (and repeatedly reminding me that “for…in” is not the same as “for…of”, which I think I managed to do wrong every single time, which again tells you how much Javascript I usually write).
Feel free to examine it (or play it) here. Note that I did eventually pull out the CSS and part of the JS after I wrote the other two versions.
HTML Canvas
Next I decided to re-implement the game using the HTML canvas. I would like to disclose in advance that I literally didn’t have any idea what the canvas was until about the day before I started this part.
Rewriting this code to use the canvas was… actually really easy. I hadn’t worked that hard on organizing it at this point, but for the most part the drawing logic was separate from the game logic, and the canvas is just not that hard to use. Basically it’s just a bunch of function calls to draw lines and shapes and so forth. So, oops. I guess I should have learned this earlier.
One thing I quickly ran into was that drawing pixel-specific coordinates is strange because the canvas apparently considers e.g. the first pixel to span from 0 to 1, so if you want to draw a line that is centered on that pixel, your line has to start at 0.5. This initially made all my gridlines seem “blurry”. Apparently an easy workaround for still using whole numbers is to translate the canvas itself by half a pixel (see StackOverflow).
On a somewhat-related note, when I tried to draw squares within the gridlines, I wound up accidentally creating this weird trailing effect where it would slowly write over the gridlines and leave behind a vague impression of the moving shapes:
This honestly looks kinda cool, but it wasn’t what I was going for, so it’ll have to go.
Getting rid of this artifact was a bit trickier than I initially expected - it seems like it’s hard to draw sharp edges “right next to” each other, presumably because antialiasing is enabled on the canvas by default (and although there is a property to turn it off, it doesn’t seem to have the effect I would expect). After trying a couple of things, I settled on simply redrawing the gridlines after drawing the filled squares, so I could draw them in overlapping spaces without worrying about it.
At this point I’m done - the game again looks like it’s supposed to, and I didn’t change the fundamental game logic at all, so it still works as before. Final version here.
SVG
Finally, I tried implementing a version of the game that used Scalable Vector Graphics (SVG).. This was likewise pretty easy to use, although I did run into a couple of random HTML/JS tidbits:
- Creating an SVG item requires that you use e.g.
document.createElementNS('http://www.w3.org/2000/svg', 'rect')
- Presentation attributes need to be set with
.setAttribute(...)
, although they can be overridden by CSS styles (see CSS-Tricks for a detailed explanation) - If you only use CSS you can run into what people call the “Flash of Unstyled SVG” where if your CSS fails to load or loads slowly, you initially see the SVG without styling, so it’s a good idea to set presentation attributes to sensible defaults
Unlike the canvas, SVG lines are crisp by default and I didn’t have to work hard to make my squares look exactly like I wanted. (I guess it remains to be seen how well this works out for other applications - Tetris is by definition a bunch of squares on a grid, whereas for most graphics you might want something closer to what the canvas does.)
This felt a lot closer to manipulating the DOM, although the function calls to do so felt a little clumsy by comparison (lots of calls to “setAttribute”). Unlike the canvas, SVG elements literally exist on the page persistently, so you never have to just redraw the whole scene from scratch. On the flip side, they aren’t in any kind of tabular format like the table cells were from the first implementation, so I had to essentially impose a numbering system on them using their ids instead. This only really amounted to a couple of lines of code total, though.
You can also quickly put together SVGs in separate editors like Inkscape or Adobe Illustrator, although apparently both also can export to the Javascript necessary to draw to a canvas.
Anyway, the SVG version is here.
Adding Animation
This was new for me as well - I decided to use CSS to add some additional highlighting to the active blocks. I initially started with the “table” version of the page and naively tried to add an animation to the table cells themselves, but as the active piece dropped I started getting something like this:
(For reference, the animation is to briefly brighten the piece from being a particular color to being white.)
The problem here is that the animation basically starts running when the class is changed to reflect the cell being an active piece, but when the piece moves, inevitably some of its cells already had that class, so the animation keeps running from the same point, while the new cells start running it from the beginning. (Initially I tried to fix this by making sure the animation ran at exactly the rate the pieces fell, and then immediately ran into the same issue as I moved them left/right.)
I thought about this and decided on an alternative: I made the background of the table itself actually be “the color of the active piece”, and animated it, and made the table cells for the active piece transparent. All non-active cells would be colored (including the black for the empty cells). This worked properly.
For the canvas version, since I’m just manually painting everything, I can trivially paint different things whenever I want. This does require me to repaint more often, though, as well as to manually interpolate the color.
SVGs have their own animation process. I could probably cheat and do much the same thing as with the table cells (and potentially avoid SVG animation altogether), but I think I’ll save it for a time when I can figure it out, so for now the third version doesn’t have the animation.
What I Learned From This
Since the main point of all of this was learning/practice, I feel like I should keep track of what I got out of it (most of which is probably already obvious):
- Rendering using SVGs
- Dynamically creating SVG objects
- Styling SVG objects
- Rendering using the canvas
- Practice using classes in JS
- General HTML/CSS practice
- Basic CSS animation
Remaining Issues
The three implementations are far from perfect, so here’s a list of issues I might deal with if I am motivated to work on them further:
- All three implementations share a game-logic class, but also have considerable duplicate logic for how they interact with the pages (and near-duplicate HTML), which should be combined.
- As noted above, I never animated the SVGs.
- The animations don’t turn off when the game is over (and probably should).
- The SVG version has slightly weird borders - I think all of the inner borders are actually two border lines stacked together and the outer borders are one, so if you squint or zoom in you’ll see that they aren’t the same width.
- Something is slightly different between the layout of the first version and the other two (most obvious if you look at where the game board appears on a full-screen Chrome window on a large monitor).
- The SVG version is a bit too much like the table version in that it repeatedly assigns the colors of every “cell” (or
<rect>
). This didn’t feel very idiomatic to me. I think a better solution might be to actually move the active<rect>
s down until they land, but this would require me to modify (or write a different version of) a bunch of the other code, which is designed with the idea in mind of some form of a complete redraw on each change.
References and Further Reading
I tried to keep track of the articles I used to get up to speed, although I think I probably consulted W3Schools and MDN a bunch:
CSS - An Art, a Science, a Nightmare (Everything You Should Know)
Presentation Attributes vs Inline Styles
CSS Animation for Beginners
A Guide to SVG Animations