BlackOmen C++11 game engine part 1: Platform definition

31 03 2013

Hello all and welcome to the first part in a series of articles about my personal game engine I’m currently developing in my spare time. I called it BlackOmen like the last dungeon in Chrono Trigger. It is coded in C++11, the latest standard of C++.

My goal with this series is to teach you what I learned along the way developing my engine. Even if I work professionally in the game industry, I still have much to learn about engine programming so feel free to give me constructive comments.

First of all, I want to talk to you about a fundamental header I’ve seen in many libraries, the platform definition header. This header contains type definition and macro for identify which platform your code is currently being compiled.  It is very useful to make your code more portable.

What’s the big fuzz about types ?

Types are a fundamental part on any programming language. They help you tell the compiler how you want your variable to be stored and interpreted in memory. In a game, you are dealing with lots of data and knowing how your data is stored in memory can be critical in certain situations. Types are defined by the kind of data they represent and their size. They also have different size depending on which compiler and/or platform you are using.

As an example, the signed types in the Microsoft C++ compiler are defined like this:

Type name Size in memory (bytes) Range
int 4 –2,147,483,648 to 2,147,483,647
bool 1 false or true
char 1 –128 to 127 by default
0 to 255 when compiled with /J
short 2 –32,768 to 32,767
long 4 –2,147,483,648 to 2,147,483,647
float 4 3.4E +/- 38 (7 digits)
double 8 1.7E +/- 308 (15 digits)

I want to point you to the char type. It says that depending on a compiler switch, it can be signed or unsigned. If you are using char to store a character, it’s fine. But if you use char naively to store a number of 1 byte size, you can expect different results depending of which platform you are and can cause weird bugs. If you want consistent behavior across the platforms you are supporting, you need to create typedef for each kind and size of type you want to use.

My platform definition header

In my engine, I created a file called platform_types.h. It contains all the required typedef and macro for platform support.

It starts with this:

Platform support

#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
	#include "platform_windows.h"
#else
	#error "Please define a platform"
#endif

To ease maintenance, I decided to split platform support in a separate header so I can handle the various compilers for that platform. To identify the platform, I use macros that I’m 100% sure that are defined for the given platform. On Windows, if _WIN32 or _WIN64 is defined, you are pretty sure you are compiling on Windows. Notice also the
__CYGWIN__ test to make sure I support Cygwin also. I still only support Windows in my engine but I included an #error preprocessor notification to remind me to create the platform support header only I port my engine to another platform.

Windows platform header

Here’s how platform_windows.h looks like

#define BLACKOMEN_PLATFORM_WINDOWS

#if defined(_MSC_VER)
	#if defined(_WIN32)
		#define BLACKOMEN_COMPILER_VISUALCPP_X86
	#elif defined(_WIN64)
		#define BLACKOMEN_COMPILER_VISUALCPP_X64
	#endif
	#define BLACKOMEN_COMPILER_VISUALCPP

	#pragma warning(disable: 4127) // Disable warning C4127: conditional expression is constant
#elif defined(__CYGWIN__)
	#define BLACKOMEN_COMPILER_CYGWIN
#else
	#error "Define the compiler typedef"
#endif

#if defined(BLACKOMEN_COMPILER_VISUALCPP_X86)
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned int uint;
typedef unsigned int uint32;
typedef signed int sint;
typedef float float32;
typedef double float64;
typedef long long int64;
typedef unsigned long long uint64;
#elif defined(BLACKOMEN_COMPILER_VISUALCPP_X64)
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned int uint;
typedef unsigned int uint32;
typedef signed int sint;
typedef float float32;
typedef double float64;
typedef long long int64;
typedef unsigned long long uint64;
#else
	#error "Please define the types for your compiler
#endif

#if defined(BLACKOMEN_COMPILER_VISUALCPP)
	#define ForceBreakpoint() __debugbreak()
#else
	#error "Please define the ForceBreakpoint for your compiler"
#endif

First of all I created my own define BLACKOMEN_PLATFORM_WINDOWS to allow me to put Windows specific code inside a define if I want to.

After that, I try to identify which compiler I use to create my typedef of the basic types. I also use this occasion to disable some annoying false positive warnings per compiler. When you are compiling with warning-as-errors, those false positives become quite annoying. As before, I add #error to remind me myself to add support for additional compilers if required.

Then come the meat of the header, The typedef. For each basic type, I create a new name with the size of the type specified for it, expect for byte. I decided also to name double float64 to emphasis the fact it’s a float but with greater size.

And at last, I add a nifty macro which can be handy while debugging called ForceBreakpoint(). It forces the debugger to break at that exact line. Useful when you want to debug a specific object and the conditional debugging is too slow. I might move this macro outside of platform specific headers in the future thought.

Target macros

Next in my header is the target macro definition.

#if defined(DEBUG) || defined(_DEBUG) || defined(_BLACKOMEN_DEBUG)
#define BLACKOMEN_DEBUG
#elif defined(_BLACKOMEN_FINAL)
#define BLACKOMEN_FINAL
#elif defined(NDEBUG) || defined(_BLACKOMEN_RELEASE)
#define BLACKOMEN_RELEASE
#endif

It is used to create specific code depending of which target I’m compiling. For example, my debug info code is not compiled for the Final target. The target I’m using are Debug, Release and Final. Debug is the version with no optimization. Release is the version with full optimization but with debug information on. Final is the final release of the game with full optimization and no debug information.

I use the C++ preprocessor definitions in the project settings to set _BLACKOMEN_DEBUG, _BLACKOMEN_FINAL and _BLACKOMEN_RELEASE depending of the target.

DLL visibility macros

Next up is the DLL visibility macros. In a shared dynamic library, you can set the visibility of any function or class (symbols) to be public or private.   This is done using various compiler specific attribute you can’t afford to use directly if you want portable code.

#if defined BLACKOMEN_PLATFORM_WINDOWS
#define BLACKOMEN_DLL_EXPORT __declspec(dllexport)
#define BLACKOMEN_DLL_IMPORT __declspec(dllimport)
#define BLACKOMEN_DLL_LOCAL
#elif __GNUC__ >= 4 || defined __clang__
#define BLACKOMEN_DLL_EXPORT __attribute__ ((visibility("default")))
#define BLACKOMEN_DLL_IMPORT __attribute__ ((visibility("default")))
#define BLACKOMEN_DLL_LOCAL __attribute__ ((visiblity("hidden")))
#else
	#define BLACKOMEN_DLL_EXPORT
	#define BLACKOMEN_DLL_IMPORT
	#define BLACKOMEN_DLL_LOCAL
#endif

#ifdef BLACKOMEN_DLL
    #ifdef BLACKOMEN_BUILDING_DLL
        #define BLACKOMEN_PUBLIC BLACKOMEN_DLL_EXPORT
    #else
        #define BLACKOMEN_PUBLIC BLACKOMEN_DLL_IMPORT
    #endif
    #define BLACKOMEN_LOCAL BLACKOMEN_DLL_LOCAL
#else
    #define BLACKOMEN_PUBLIC
    #define BLACKOMEN_LOCAL
#endif

On a Windows system, you need two attribute to make DLL visibility works. The first one is BLACKOMEN_DLL_EXPORT when you are buidling the DLL itself which tells the linker to make the symbols (functions or class) public. And when you are using code from the DLL, you need to specific BLACKOMEN_DLL_IMPORT for the linker to correctly import the symbols. This is why I use BLACKOMEN_BUILDING_DLL to switch between the two. On Windows BLACKOMEN_DLL_LOCAL does nothing.

On an UNIX/Linux system, either you set the visibility to default or to hidden.  BLACKOMEN_DLL_EXPORT and BLACKOMEN_DLL_IMPORT are the same here. BLACOMEN_DLL_LOCAL tells the linker to hide the symbol.

In any case, if I want my symbols to be public, I use the macro BLACKOMEN_PUBLIC.

The last one is the BLACKOMEN_DLL define, it allows me to support static or dynamic library with the same code.

I don’t compile my engine in a DLL yet but I just put the macros there just in case I need them one day.

Engine specific types defitions

The last section on my platform definiton file is where I defined useful typedef to use for my game engine.

typedef const char* cstring;
typedef const float32 timestep_t;
typedef int64 system_tick_t;
typedef float64 gametime_t;

cstring is just an alias for a C-style string. timestep_t is the delta time between frames, I wanted this to be explicit in the code so that’s why I created a typedef. Same for system_tick_t and gametime_t. system_tick_t is the system time from a high-precision timer and gametime_t is the elapsed time since the start of the game.

How I include this header

Instead of including this header is every file that I use, I found a compiler switch that does it for me.

In Visual Studio, it is here:
VS_ForcedIncludes

In GCC and clang, it’s the flag –include that does this trick.

I agree it’s not the most portable solution but it’s works for me for now.

Conclusion

Having a platform defintion header is a key part in making your software portable across different compilers and systems. It is also a handy place to define DLL visibility macros and common types you use in your library/engine.

Advertisements




Create your own scripting language in C++ with Qt (Part 1)

28 12 2011

I’m being writing my own scripting language in Qt for the past month or so. My original goal was to have immediate feedback when trying various drawing methods using Qt drawing class QPainter. First I thought I could use QtScript and make it a 2 week programming task, but I decided to spice it a bit and write the script language from scratch.

In this series of blog posts, I’m doing to teach you how I did it. I assume you know a bit about regular expressions, C++ and Qt. I will try to simplify the theory behind the best I can.

Bear in mind that my method is not the most efficient way to implement a scripting language. My goal with this project was to learn and have fun, not to worry about performance too much.

Basics of compilation

Overview of the process

In its simplest form, the compilation process is the act of translating an input into another format. Your C++ compiler takes a text file with instructions in it and translate it into machine code.

A compiler needs several building blocks to understand a language and create another output. There are the lexical analysis (Lexer), the grammar analysis (Parser),  an abstract syntax tree (AST) and an Interpreter or a CodeGenerator in a compiled language. I left out the semantic analysis because in our case, it will be done in the interpreter.

What’s lexical analysis and grammar analysis ? We going to find out in the following sections.

Lexical analysis

First let’s take a English sentence and identify the various lexical class.

My father gave me a nice gift.

If you were paying any attention in our English or French class. You will identify these as

Fragment Lexical class
My article
father noun
gave verb
me pronoun
a article
nice adjective
gift noun

Identifying the various components of a sentence helps us in the grammar analysis by giving us a simplified representation of the sentence. If we put the sentence like this:

article noun verb pronoun article adjective noun

It is now easier to tell that our sentence is grammatically correct. If we shuffle the words around a bit like this:

father my me gave nice a gift ->
noun article pronoun verb adjective article noun

We’re still able to identify the various components but the sentence make no sense from a grammar point of view. A noun is not supposed to be followed by a article for instance.

In a programming language, we can apply the same process

void PrintDebugInformation(const char *format, int arg1)
{
    printf(format,arg1);
}

Here’s the various components

Fragment Lexical class
void KEYWORD_VOID
PrintDebugInformation IDENTIFIER
( LEFT_PARENTHESIS
const KEYWORD_CONST
char KEYWORD_CHAR
* POINTER_SIGN
format IDENTIFIER
, COMMA
int KEYWORD_INT
arg1 IDENTIFIER
) RIGHT_PARENTHESIS
{ LEFT_BLOCK_SIGN
printf IDENTIFIER
( LEFT_PARENTHESIS
format IDENTIFIER
, COMMA
arg1 IDENTIFIER
) RIGHT_PARENTHESIS
; STATEMENT_END
} RIGHT_BLOCK_SIGN

Thus the job of a Lexer is to identify lexical classes, called tokens, from the input text. Like the English grammar analysis, it will simplify the grammar analysis. Note that I named the lexical class all in caps. It helps to separate them from a grammar rule.

Grammar analysis

A grammar is a set of rules to describe a syntax, A syntax is how the lexical classes are structured together to create a cohesive and logical form.  Let’s take a C function call as an example.

printf(format,arg1);

The lexical analysis give us this:

IDENTIFIER LEFT_PARENTHESIS IDENTIFIER COMMA IDENTIFIER RIGHT_PARENTHESIS STATEMENT_END

We could formulate a rule to identify a function call like:

functionCall ::= IDENTIFIER LEFT_PARENTHESIS IDENTIFIER COMMA IDENTIFIER RIGHT_PARENTHESIS STATEMENT_END

But it assume we pass only 2 arguments to our functionCall. What if we want to support 0, 1 or more arguments ? What if we could use the zero-or-more operator(*) from regular expression ? Using the * operator, our functionCall rule looks like this

functionCall ::= IDENTIFIER LEFT_PARENTHESIS IDENTIFIER (COMMA IDENTIFIER)* RIGHT_PARENTHESIS STATEMENT_END

Okay, now we can express various function arguments, but what if our functionCall does not have arguments ? We can use another regular expression operator, the zero-or-one operator(?) on the first IDENTIFIER after the parenthesis to parse the functionCall correctly.

functionCall ::= IDENTIFIER LEFT_PARENTHESIS IDENTIFIER? (COMMA IDENTIFIER)* RIGHT_PARENTHESIS STATEMENT_END

We should be fine with our functionCall rule right ?  What if we want to pass a number to a function ? We could use the alternative operator(|) right ?

functionCall ::= IDENTIFIER LEFT_PARENTHESIS (IDENTIFIER|NUMBER)? (COMMA (IDENTIFIER|NUMBER))* RIGHT_PARENTHESIS STATEMENT_END

While it could work if we only want to support variables and numbers as function arguments, it is not flexible enough if we want to support different types of arguments like a math expression, a function call, etc. We will use another grammar rule to express the term of a functionCall

term ::= IDENTIFIER | NUMBER
functionCall ::= IDENTIFIER LEFT_PARENTHESIS term? (COMMA term)* RIGHT_PARENTHESIS STATEMENT_END

During the parsing process, the term rule in the functionCall rule will be expanded into the right lexical class. Here’s an example of a concrete parse tree of the function call defined above. As you can see, our lexical classes are all leaves in the tree. The goal of a rule is to create leaf nodes in a parse tree (conceptually of course).

The Parser job is to take a text input and validate the syntax of a language. It uses the grammar rules to define the order of parsing. But it can do more than just validate the syntax of a language. For each rule, we can add code to be executed each time the parser encounter that rule. It can be used to create the nodes in the Abtract Syntax Tree for instance.

In a next part, we’ll go deeper into defining grammar rules and how it translate to C++ code.

Abstract Syntax Tree

An abstract syntax tree is a tree thats represent operations in your language. It is called abstract because it doesn’t store the whole source code, it only store the data needed for each operation. Why we don’t store the whole information ? Because most information is redundant or only used to identify a language construct. Let’s take a variable declaration statement.

int myVariable = 3*5*x;

It generates this Abtract Syntax Tree:

As you can see, the int type, equal sign and the semicolon are not expressed in the tree. We just don’t need them. I also put the data stored in each node to show that an abstract syntax is not a dumb representation of the source code syntax, it is used for interpretation or code generation.

Interpreter

An Interpreter takes an AST, traverse it and execute an operation on each node. In a nutshell, it’s the Interpreter pattern in the GoF Design Patterns book. We could also use the Visitor pattern to visit each node and make the AST independent of the code execution mechanism (ex: interpreted, JIT, compiled)

Conclusion

In this part, I introduced you to various concepts related to compilation: lexing, parsing, abstract tree and interpretation. In the next part, we will start coding the scripting language by implementing mathematic expression in our scripting language. Don’t worry, I won’t fall in the trap of others compilations tutorial, I will go further than math expressions 🙂





Refactoring game legacy code Part 2 – Cleaning up the terrain

29 07 2009

In last episode, I introduced my project about refactoring my Tetris clone code.

In order to get a feel of the code, I’m going to do some small mechanical refactorings. I will do only safe renames and extract method in the class related to Block and Piece. I will not try to alter behavior before I write my characterization tests (will explain in a later article). I am using Visual Studio 2005 and Code Rush(which include Refactor! C++) free version. Visual Assist X will be fine too but it costs money.

Let’s go deep into the code !

The first thing I noticed when reading the code related to Piece and Block was this:

void Piece::moveBlocks(int deltaX, int deltaY)
{
 for(int i=0; i<TetraSize; i++)
 {
  m_tetra&#91;i&#93;.move( m_tetra&#91;i&#93;.x() + deltaX, m_tetra&#91;i&#93;.y() + deltaY );
 }
}
&#91;/sourcecode&#93;

Why do I need to query the X and Y coordinates of each block forming the tetra in order to move them ? This is a violation of the principle "<a title="Tell, don't ask" href="http://www.pragprog.com/articles/tell-dont-ask">Tell, don't ask</a>". I'm going to refactor in small steps (like strongly suggested in Martin Folwer's Refactoring).

First I renamed the <code>move</code> method of Block class to <code>moveAbsolute</code> since it's clearly what this method is doing. After, I created a new method called <code>Block::move</code> which really moved according to deltaX and deltaY. Then, I looked up all the calls of moveAbsolute and make then use the new <code>move()</code> if they required this new behavior.

I found a interesting thing. All calls to <code>moveAbsolute</code> are in my poor handling of the Piece. Thus in the future, we will be able to remove this method after several refactorings. This single rename make my bad code stood out of the rest.

After, my random look into the code, I saw this big function:


SDL_Surface *BlockSurfaceManager::get(BlockColor color)
{
 if( d_ptr->surfaceCache.count(color) == 0 )
 {
  SDL_Surface *newSurface = SDL_CreateRGBSurface(SDL_HWSURFACE, BlockWidth, BlockHeight, ColorDepth, 0, 0, 0, 0);

  SDL_Rect blockRect;
  blockRect.x=0;
  blockRect.y=0;
  blockRect.w=BlockWidth-1;
  blockRect.h=BlockHeight-1;

  Uint8 r=0,g=0,b=0;
  switch(color)
  {
   case BlockCyan:
    r=0; g=240; b=240;
    break;
   case BlockBlue:
    r=0; g=0; b=240;
    break;
   case BlockOrange:
    r=240; g=160; b=0;
    break;
   case BlockYellow:
    r=240; g=240; b=0;
    break;
   case BlockGreen:
    r=0; g=240; b=0;
    break;
   case BlockPurple:
    r=160; g=0; b=240;
    break;
   case BlockRed:
    r=240; g=0; b=0;
    break;
  }

  SDL_FillRect(newSurface, &blockRect, SDL_MapRGB(newSurface->format, r,g,b));

  d_ptr->surfaceCache[color] = newSurface;
 }

 return d_ptr->surfaceCache[color];
}

As you can see, the if block is creating a new surface if the color has not been asked yet. The problem is this block is in the way of the main logic of my method, which is to retrieve a cached surface for the given color. Using “Extract Method”, I can make my intent more clear.

SDL_Surface *BlockSurfaceManager::get(BlockColor color)
{
 if( d_ptr->surfaceCache.count(color) == 0 )
 {
  createCachedSurfaceFor(color);
 }

 return d_ptr->surfaceCache[color];
}

I could go very zealot while refactoring this class. For the intend of this article, I’m going to ‘give my life for Aiur’ errrr make my intention even more clear 🙂

I will extract the if condition into its own method:

SDL_Surface *BlockSurfaceManager::get(BlockColor color)
{
 if( hasNoCachedSurfaceFor(color) )
 {
  createCachedSurfaceFor(color);
 }

 return d_ptr->surfaceCache[color];
}

bool BlockSurfaceManager::hasNoCachedSurfaceFor(BlockColor &color)
{
 return d_ptr->surfaceCache.count(color) == 0;
}

As you can see, it makes my intent more clear. Also, when you extract method with CodeRush, please double check the header if your method has the correct visibility.

For the beverity of the article, I will not describe in details the mechanics of the refactoring of createCachedSurfaceFor() but here’s for your viewing pleasure:

void BlockSurfaceManager::createCachedSurfaceFor(BlockColor &color)
{
 d_ptr->surfaceCache[color] = createBlockSurface(color);
}

SDL_Surface* BlockSurfaceManager::createBlockSurface(BlockColor &color)
{
 SDL_Surface *newSurface = SDL_CreateRGBSurface(SDL_HWSURFACE, BlockWidth, BlockHeight, ColorDepth, 0, 0, 0, 0);

 Uint8 r=0,g=0,b=0;

 getBlockColorRgb(color, r, g, b);

 drawBlockSurface(newSurface, r, g, b);

 return newSurface;
}

void BlockSurfaceManager::drawBlockSurface(SDL_Surface *newSurface, Uint8 &r, Uint8 &g, Uint8 &b)
{
 SDL_Rect blockRect = createBlockRect();
 SDL_FillRect(newSurface, &blockRect, SDL_MapRGB(newSurface->format, r,g,b));
}

SDL_Rect BlockSurfaceManager::createBlockRect()
{
 SDL_Rect blockRect;
 blockRect.x=0;
 blockRect.y=0;
 blockRect.w=BlockWidth-1;
 blockRect.h=BlockHeight-1;
 return blockRect;
}

void BlockSurfaceManager::getBlockColorRgb(BlockColor &color, Uint8 &r, Uint8 &g, Uint8 &b)
{
 switch(color)
 {
 case BlockCyan:
  r=0; g=240; b=240;
  break;
 case BlockBlue:
  r=0; g=0; b=240;
  break;
 case BlockOrange:
  r=240; g=160; b=0;
  break;
 case BlockYellow:
  r=240; g=240; b=0;
  break;
 case BlockGreen:
  r=0; g=240; b=0;
  break;
 case BlockPurple:
  r=160; g=0; b=240;
  break;
 case BlockRed:
  r=240; g=0; b=0;
  break;
 }
}

The code has more lines, but the intend of the code is more clear. I stop here for the moment but I should really factor the surface creator into its own class. BlockSurfaceManager has two responsibilities: Managing the surface cache AND creating the surface. This does not follow the Single Responsibility Principle where each class should have one reason to change.

I will commit the changes I’ve made. Let’s return back to Piece. In Piece::rotate() I have this snipped of code

if( type() == Piece_Square )
 {
  m_direction = newDirection;
  return;
 }

 Block *rotatedBlocks = getBlocks(newDirection);
 if( tryMove(0, 0, rotatedBlocks) )
 {
  delete[] m_tetra;
  m_tetra = rotatedBlocks;
  m_direction = newDirection;
 }
 else
 {
  delete[] rotatedBlocks;
 }

I smell something bad in here. The base class make assumption about its derived classes. I will leave it here for now because I haven’t written my test and I could alter behavior by removing the faulty block.

Here’s another obscure method:

bool Piece::tryMove(int deltaX, int deltaY, Block *block)
{
 bool canMove=true;

 for(int i=0; i<TetraSize; i++)
 {
  if( (block&#91;i&#93;.x() + deltaX) >= BoardEndX ||
   (block[i].x() + deltaX) < BoardX ||
   m_board->blockAt(block[i].x() + deltaX, block[i].y() + deltaY) ||
   (block[i].y() + deltaY) < BoardY ||
   (block&#91;i&#93;.y() + deltaY) >= BoardEndY
   )
  {
   canMove=false;
   break;
  }
 }

 return canMove;
}

The if condition is confusing as hell. With simple reordering and extract method, it becomes this:

if( isOutsideBoard(block[i], deltaX, deltaY) ||
   hasBlockingBlock(block[i], deltaX, deltaY) )
  {
   canMove=false;
   break;
  }
bool Piece::isOutsideBoard(const Block &block, int deltaX, int deltaY) const
{
 return (block.x() + deltaX) >= BoardEndX ||
   (block.x() + deltaX) < BoardX ||
   (block.y() + deltaY) < BoardY ||
   (block.y() + deltaY) >= BoardEndY;
}

bool Piece::hasBlockingBlock(const Block &block, int deltaX, int deltaY)
{
 return m_board->blockAt(block.x() + deltaX, block.y() + deltaY);
}

Make much more sense. Let’s commit this. As for the derived classes of Piece, I won’t ever dare to touch them before I write some tests.

Let’s call done for the day. In the next episode, I will show you how to make the code into test harness.





How to make your internal classes accessible to your unit tests in .NET

27 07 2009

Here a little tip I’ve found while reading Moq source code. If you want to test some internal classes of your library, you don’t have to make your classes public in order to be visible in your test assembly (like how I was doing untill now…)

The trick is to add this little line into your assembly properties (AssemblyInfo.cs)

[assembly: InternalsVisibleTo("NameOfYourTestAssembly")]

Now all class with the internal visibility will be accessible to your unit tests. For private visibility, one solution is to use reflection or just look on Google 😉





Refactoring game legacy code: Part 1 – Introduction

26 07 2009

Introduction

Hello, welcome to my series of article about refactoring game legacy code. Game legacy code is no different than other types of code. What’s makes game different than others type of codes ? With a game, you are dealing with a event driven, simulated real-time architecture. They have different needs and requirements than a business, Web or rich client application. Games need to react fast to events such as ‘gun was triggered’, you have parts of your code that are called 30 or 60 times per second, loading times need to be short, etc.

There is a lot of things going on in a game, where in a typical business application you just need to collect some input, process it and persist into a database (good ‘ol CRUD).

The subject

The subject is my first complete game ever written. If you look at the projects sections of this site, you’ll see some failed attempts. The game is a Tetris clone called “That Game From Russia (you know it)”. It is written in C++ using SDL library. It was done in two weeks back in January 2009. If you have read my article Paradigm Shift, you know this was before I read many books and articles that changed my way of thinking about code, design and architecture.

The code can be found at http://code.google.com/p/gamefromrussia/

We start with SVN revision 5.

Current state of the architecture

The architecture is based on a basic state machine. There is a bit of duplicated code scattered around the code, like the input handling. The code is pretty tightly coupled. They are little to no abstraction in the code. SDL types are found in public API. When I created the code, I intended to do my Tetris in one day. Yeah silly I know. I didn’t intend to create a flexible architecture. I wanted to create a Tetris for fun thus I didn’t try to care that much about the craft of my code.

Anyway so you have the states (DropPiece, InitGame, Setup, GameOver, RemoveLines, SetNextPiece, PauseState, LinesRemoveAnimation) that define the core behavior of the game. Each state has a enter(), execute() and exit() methods. enter() initialize the state, execute() is called on every iteration of the game loop and exit() is called before changing to another state.

The “domain” or the core of the game are in the classes Block, BlockLine, Board, Piece. Block is a single block. BlockLine is a line of blocks in the Board. Board contains a list of block lines. Piece is a collection of blocks that represent the shapes in a Tetris game.

The other classes are support classes. SoundPlayer manages sound effects and music. TextPrinter manage drawing the current level, the score and the number of lines.

What we are trying to refactor ?

What I want to refactor is all the code related to the Piece handling. I want to make the rotation code easier to understand. Currently, for each type of piece, you have obscure code that move the blocs depending of the direction. It assume that the x, y is for the center block and the other blocks are moving according to this block. While developing this code, I had sketches on paper to help me map the blocks for each piece. I shouldn’t needed a piece of paper to manage the complexity of this code.

The right way to rotate a piece is to use a rotation matrix and apply it to each of the blocks coordinate. I found this when looking at other Tetris clone code. You don’t need to use a matrix explicitly thought. I will explain the solution a little deeper in a following article.

When you refactor code, you should keep in mind your main goal. When dealing with legacy code, you can be tempted to do large scale refactoring of your ugly, stupid code. Having a goal help you focus of refactoring code that matters for what you are trying to accomplish.

Why I am doing this ?

In this case, I want to train my refactoring, TDD, SOLID skills in a C++ code base in preparation for a much larger game project this fall. Also, I am to experiment some techniques illustrated in Michael C. Feathers book Working Effectively with Legacy Code.

Refactoring legacy code is hard

I don’t claim to be an expert in refactoring legacy code. In fact, it’s quite the opposite. I am still a beginner in refactoring legacy code. Up to this point, I only refactored one legacy code base I written in C# back in 2007. It is quite a hard journey depending of the quality of the code base. My current code base has a lot of inner, subtle dependencies that we will need to break over time.

Next episode

In next episode, we’ll start doing some code cleanups to get a feel of the code base before setting up the tests in preparation for larger refactorings.