As a mini-project to get my hand back in with C++ and to learn the Cairo graphics library, I made a Tetris clone. It is surprisingly playable! You can download an executable here and the source code here (bear in mind that the quality of the code is not high, more later), it requires Windows, but not a particularly recent version (probably). I am pleased that it looks and plays so well, and pleasantly surprised how quickly the C stuff came back after a few years away from it. The code itself is bordering on the trivial, but I spent a long time looking up library stuff, so it took longer than expected. Cairo, it turns out, is a nice graphics library to use.
Since this is more of a learning exercise than anything else, the project is far from perfect. Rather than spend lots of time making it so, I thought it would be more beneficial to critique the code:
'Requirements'
I am happy with how the game plays and it has all the main features of Tetris. It is deliberately missing quite a lot too: score, high score board, music, sound effects, an intro screen, the 'B' game, and probably a load more I can't remember. I could also spend more time tweaking the piece rotations and the starting positions and other general UI/presentation stuff.
I used the Windows API to make the window and handle input and the game loop/timer. This worked quite well, but has downsides - if the processor gets busy, the game stutters a little, it is not greatly efficient or robust and it is not portable. To make a really professional version, I'd want to use DirectX or something (although, that wouldn't make it very portable) and handle the game/input loop myself. I also cut a lot of corners on the Windows side of things, there is just a basic window, I should make it non-resizeable and tweak a whole load of settings etc., but that was not the focus of the project.
Design
I'm happy with the overall deconstruction into classes. I think most of the concepts are right, and the classes are about the right size. I like the use of the flyweight pattern to represent squares (i.e., the components of a piece in the board). However, there is definitely room for improvement: I would like to split the Piece class into two: one representing an actual piece in the game, and one representing a kind of piece, at the moment the responsibilities of the former are split between the Piece and Board classes. Given the small number of squares on the board, it might be nicer to have an explicit Square class, rather than using the flyweight pattern - this would allow some of the responsibilities of the Piece class to be moved to the Square class (such as colour, and drawing the piece). I like the use of a data file to initialise the pieces and the use of a linked list to store the rotations.
The Piece and Game class are very tightly coupled and this could be improved. In particular there is an implicit requirement on the dynamic interactions of these classes which couples them together. In a way, Game should be a controller class, and Board a view class, but actually Board has (and should have) some controller features. It might be best to split both Board and Game into view and controller components (and perhaps split out a model from Board); some better separation of Board and Game would still be necessary.
The Game object is a state machine, but this is implicit; I should make this explicit.
The drawing code (using Cairo) is scattered throughout the program. It could better be encapsulated in a presentation layer. The Windows code is all in the 'top' file - Tetris.cpp and doesn't pollute the rest of the program, but it should be in a class (or classes) of its own and should be better organised.
I use an array of rows, I think this is the right choice because often I use random access, but when I think about it most access is conceptually sequential - the piece drops one row at a time, I always look at adjacent rows at once (e.g., all rows occupied by the dropping piece, lines are shuffled down by one row at a time, etc.), so maybe it should be a linked list.
Tetris has a tile-based presentation (on the Gameboy, at least, which is the only real version, if you ask me) and I preserve this in my version. But internally there is no explicit tiling (except for the actual board). The drawing code would be cleaner if the tile structure were explicit.
Implementation
The overall code quality is not high: it could do with some more comments, and some better variable names. More importantly, a lot of the standard C++ defensive programming things are missing - I don't take care of copy and assignment constructors, check for errors allocating memory or doing IO (or anywhere else), there are not as many 'const' or 'explicit' declarations as there should be, etc. There should be more (some!) unit tests. Perhaps I ought to use references some places that I use pointers, but I kind of go for pointers by default.
I could probably make better use of the STL, rather than using so many explicit loops, etc.
There is not much re-use or inheritance going on. This is probably OK, since it is a small program and there are not many overlapping concepts. However, I still should think about how classes might be reused, and add 'virtual' declarations accordingly.
The use of Cairo is inconsistent (especially the use of save/restore) and pretty unsophisticated (but the whole point of this was to learn Cairo, so this is to be expected). I use the Cairo text library which is apparently just a "toy" library, so I should use something heavier-weight, but then it did the job pretty well (I also picked a slightly obscure font, so the text might not look so good on your system).
I could probably store objects that are repeatedly drawn (such as the scoreboard and walls) rather than redrawing them each frame, I didn't work out how to do this in Cairo, though. This is particularly an issue with the text (and using a stringstream just to convert from an int to a char* is overkill, but it used to do more).
This is cool, I dug into a tetris clone ( a crappy clone ) found on my raspberry pi. It used python's pygame. If I ever get back to that project I may use cairo since I was just reading it and your tetris looks alot like where I was taking mine :).
ReplyDeletehttp://i.imgur.com/mSgWJer.png
http://i.imgur.com/wSSaEZk.png
I didn't write the tetris engine, but I transformed that little python file no doubt :). I'll definitely take a look at your source, since I'm interested in finding different ways to express a puzzle piece's position and general board information