Monday, June 20, 2011

Tetris

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).

Thursday, June 02, 2011

Building Cairo in Windows

Recently, for reasons best left aside for now, I tried (and succeeded) in building the Cairo graphics library in Windows. This is a bit more complicated than it sounds (although not as bad as I expected), so I thought I would document it in case someone else wants to do it too. For the record, I am running Windows 7 (32 bit), I used a combination of Visual Studio 2010 and GCC. You'll need Git installed. I use %zlib% to mean the (top) directory where zlib is installed on my computer, and similarly for the other libraries.

Step 1 - download

You will need the following:

Cairo (git://anongit.freedesktop.org/git/cairo)
Pixman (git://anongit.freedesktop.org/git/pixman.git)
LibPNG (http://www.libpng.org/pub/png/libpng.html)
Mozilla Build Environment (http://developer.mozilla.org/en/docs/Windows_Build_Prerequisites#MozillaBuild)
ZLib (http://zlib.net/) (sort of optional)

You will need to clone the first two Git repositories and download and extract the source zip files for the latter two (or three).


Step 2 - ZLib

You can build ZLib if you like. I built it, but got linking errors later on, instead I used the ZLib static library which builds as part of LibPNG (more later). If you want to do it, it's straightforward:

First you need to build the assembly files, run bld_ml32.bat found in %zlib%\contrib\masmx86. You will need to do this from the Visual Studio command prompt, not the usual windows one.

Open %zlib%\contrib\vstudio\vc10\zlibvc.sln in Visual Studio.

Change the configuration to 'Release' and build it. That should be it.


Step 3 - build LibPNG

Open %libpng%\projects\vstudio\zlib.props in a text editor and change the value of '' to %zlib%. (I used the zlib library which I downloaded, there is a ZLib library included with LibPNG and you could probably use this here, somehow). Save and close the file.

Open %libpng%\projects\vstudio\vstudio.sln in Visual Studio. Change the configuration to 'Release Library' and build it.


Step 4 - build Pixman

I couldn't figure out how to build Pixman or Cairo in Visual Studio, I got close with Eclipse, but couldn't quite figure it out using the Eclipse build system. In the end, I resorted to automating the following method using a batch file and a shell script (see below) and launching that when building from Eclipse. I expect I can do the same thing for Cairo, but haven't yet.

You might need to create pixman-version.h from pixman-version.h.in and configure.ac, I did this manually, but it might get done automatically.

Open %mozbuildenv%/start-msvc10.bat in a text editor and add the following lines:
set LIB="%LIB%;%zlib%\contrib\vstudio\vc10\x86\ZlibStatRelease;%libpng%\projects\vstudio\Release Library;%pixman%\pixman\release"
set INCLUDE="%INCLUDE%;%zlib%;%libpng%;%pixman%\pixman;%cairo%\src;%cairo%\boilerplate"

Note that '%LIB%' and '%INCLUDE%' are literal strings here.

Save the file and close it.

From the command line (Windows or Visual Studio), run %mozbuildenv%/start-msvc10.bat, cd to %pixman%\pixman. Run "make -f Makefile.win32 CFG=release OUT_PATH=%outpath%", where %out path% is the path where you want to output the compiled Pixman library.

That's all you need to do a manual build. To automate it from Eclipse, you will need to create the following batch and shell scripts and run the batch file as the build command in Eclipse. You will also need to set the MINGW_HOME variable in the Eclipse project properties.

build_pixman.bat:

@echo off
del %pixman%\pixman\release /Q
%mozbuildenv%\start-msvc10.bat %pixman%/build.sh

build_pixman.sh:

#!/bin/sh
cd %pixman%/pixman
make -f makefile.win32 CFG=release

Note that the %paths will need to be in Windows format in the first two cases, and *nix format in the latter two. You could do the same for Cairo.


Step 5 - Build Cairo

I needed to apply a couple of patches before I could build Cairo. If you get a more recent version these patches may already have been applied, or be unnecessary. The patches are at http://lists.cairographics.org/archives/cairo/2011-May/021927.html and http://lists.cairographics.org/archives/cairo/2011-June/021985.html. Apply these using Git.

Open %cairo%/build/Makefile.win32.common in a text editor and find the reference to 'zdll.lib' and change it to 'zlib.lib'. If you do not have the zlib, libpng, pixman, and cairo directories nested in the same directory, you will need to change the paths in this file accordingly. Save and close the file.

Copy and rename %libpng%\projects\vstudio\Release Library\libpng15.lib to %libpng%\libpng.lib. Copy %libpng%\projects\vstudio\Release Library\zlib.lib to %zlib%. Alternatively, you could change the paths in Makefile.win32.common, in fact this is a better way of doing it, if you plan to re-build libpng or zlib. Note that I am using the version zlib.lib built by libpng, I could not get Cairo to build using the version built by zlib - I got a bunch of errors linking libpng.

From the command line (Windows or Visual Studio), run %mozbuildenv%/start-msvc10.bat, cd to %cairo%. Run "make -f Makefile.win32 CFG=release OUT_PATH=%outpath%", where %out path% is the path where you want to output the compiled Pixman library. Note that you need to run the makefile in the cairo root, not the src directory.