So, I've been addicted to Tetris for the last week or so. Last night I decided to do something else, and when I came across the Phoenix LiveView contest, I thought: why not try and make Tetris? #myelixirstatus
I was surprised by how far I got. I have watched videos of people coding games before, have been coding myself for years and years, but somehow games felt out of my league. But here, after a (long) evening, I got a game! I should've streamed it myself.
I mean I'm standing of shoulders of giants. I got so much for free from Erlang/Elixir, LiveView to hook it up, @chris_mccord's Snake example gave me the basic idea of using just
<div>'s for blocks, the browser is doing the drawing and key-repeats for me.
But it felt magical, once the game got playable. I was hooked again to my own creation. And so many "features" from NES Tetris like sliding, tucking and spinning "just worked" in my first implementation (probably because I was close to their implementation).
It all started with this HTML based board, using
flexbox to to the hard works of blocks for me. I later wrapped the board in a Game-struct, and added bindings for the scores.
How to get a game loop in Elixir? Just send yourself a message after, say, 500ms. The LiveView component has a
handle_info/2, which queues the next tick and moves the current Tetromino down.
Thanks to the
phx-keydown="keydown" in the template, we get messages in
handle_event/3 for each key. Just delegating to my Game module. And yes, I use Vim, so I need those
L. (Dropping with
K is not implemented.)
Game.move/2 got a bit complicated, some refactoring is in place. But I started with a Tetromino struct, with
:y keys and a
:color. To move it down, you paint the current
:white, and then paint
:y + 1 to the
:color. Code below reduces for all four points:
To obtain all the points I got this wonderful
Tetromino.points/1 function. It's ugly and was a pain to write out and get right, but it works like a charm.
Colors and rotations work similar, with
color_for_type/1 gets called in
new/1, and it never changes once it is created.)
Then the messy
Game.move/1: can you move down? Move down. Otherwise, call
Board.clear_lines/1 to remove full lines and obtain the score, get a new random Tetromino and put it on the board. The game-over handling is a bit buggy still, I had only one evening.
Clearing the board is simple: reject rows of which all cells are not empty and use counts to add new rows to the top. The
Board.color_at/2 gives the color for that particular cell. The guards against negative numbers prevent
List.pop_at/2 to get items from the back of the list :)
I think that's mostly it! Again, I wish I had streamed it, it was very weird watching myself doing it, but also very rewarding. Keep coding, people, you can do this too!