I've always wanted to code the Tetris, but as time goes on,this game looks a bit old fashion. Especially on those new computers with all bells and whistles, but this led me to idea of writing Tetris for .NET Micro Framework. It will be cool sample application and discovery of WPF possibilities in Micro Framework. Video shows more..

Tetris based on .NET Micro Framework
Tetris game running on the Tahoe board

Two dimensional arrays in Micro Framework

Probably the biggest glitch with writing Tetris, was a fact that Micro Framework supports only one dimensional arrays. Since Tetris is about grid with falling 'grid-blocks', the two dimensional arrays are quite essential.

First step was to write ByteMatrix class which represents matrix of byte value (two dimensional byte array). Constructor of the class takes Rows and Columns argument to initiate the matrix size. Internally, values are stored in one dimensional array accessed by the GetCell(int row, int column) and SetCell(int row, int column, byte value) methods, that evaluates the appropriate index in array.

public class ByteMatrix
{
    private byte[] _baseArray;
    private int _rows, _columns;
    
    public ByteMatrix(int rows, int columns)    
    public ByteMatrix(ByteMatrix sourceMatrix)
    public byte GetCell(int row, int column)    
    public void SetCell(int row, int column, byte value)
    public void SetCells(int row, int column, byte[] inputArray)
    public void Clear()

    #region Properties
    public int Rows
    public int Columns
    public int Length
    public byte[] BaseArray
    #endregion
}

Separating logic and presentation layer

It's always better to separate business logic of the application and presentation layer. The code becomes better portable and maintainable as well as unit test of the logic are much easier to implement when UI is not considered. Tetris application has two namespaces GameLogic and Presentation. First one contains all classes for core game. These classes can be easily ported to .NET Compact Framework or full Framework because has no direct relation to Micro Framework. On the other hand the Presentation namespace consist of WPF controls and windows that are used to visualize the game and provides interaction with user.

The base of the game is the GameUniverse class which keeps game grid, current falling block and game statistics (score, level, etc.). GameUniverse can be initialized by Init() method, that clears the game grid and game statistics. Starting game in GameUniverse is done by StartLevel(int level). Argument specifies level where game will start. StepUniverse() method runs one game step: moving block one row down a test for bottom of the grid or end of game. This method is called from presentation layer by timer.

I'am using quite lot of properties on objects - just to make code more demonstrative. However, in performance critical parts of code is better to avoid them. Properties are usually about accessing object fields by methods, so it's just another call on the stack. Especially in embedded development (Micro Framework, Compact Framework) those getter / setter properties makes not to much sense.

public class GameUniverse
{
    // Game grid dimensions
    const int FIELD_COLS = 10;
    const int FIELD_ROWS = 20;
    
    private int blockX, blockY;
    private ByteMatrix currentBlock, nextBlock;
    private ByteMatrix field = new ByteMatrix(FIELD_ROWS, FIELD_COLS);
    private GameStatistics gameStats = new GameStatistics();

    public void Init()
    public void StartLevel(int initLevel)
    public void StepUniverse()
    public void StepLeft()
    public void StepRight()
    public void Rotate()
    public void DropDown()

    private void NewBlock()
    private void StepDown()
    private bool Check(ByteMatrix block, int x, int y)
    private void ProcessFullLines()

    #region Properties
    public ByteMatrix CurrentBlock
    public ByteMatrix NextBlock
    public ByteMatrix Field
    public int BlockX
    public int BlockY
    public GameStatistics Statistics
    #endregion
}

Tetris screenshots
Tetris running in Tahoe board emulator

Persisting high score table

I've never liked those games thats forgets the high score table. Since Micro Framework has good support for persisting objects, let's save high score table to flash memory. Persistence is done by ExtendedWeakReference class, which stores serializable object passed in Target property. Prerequisite for storing object into flash memory is serializability of object itself and all it's components. Following listing shows serializable HighScoreTable class with array of serializable ScoreRecord structure.
/// <summary>
/// Struct representing score record in high score table
/// </summary>
[Serializable]
public struct ScoreRecord
{
    public string Name;
    public int Score;
}

/// <summary>
/// Class to keep and work with high score results
/// </summary>
[Serializable]
public class HighScoreTable
{
    public ScoreRecord[] Table;
 
    public HighScoreTable()
    public int AddRecord(ScoreRecord scoreRecord)
}

Process of retrieving the stored high score table is in constructor of TetrisApp(). Static ExtendedWeakReference field is initialized by calling the CreateReference method, which takes three arguments. First two arguments are used to identify the object in perstistent storage. Third argument specifies the 'survival level'.


// Create ExtendedWeakReference for high score table
highScoreEWD = ExtendedWeakReference.RecoverOrCreate(
                                        typeof(TetrisApp), 
                                        0, 
                                        ExtendedWeakReference.c_SurvivePowerdown);
// Set persistance priority
highScoreEWD.Priority = (int)ExtendedWeakReference.PriorityLevel.Important;

Saved object is restored by reading from the Target property. In case that no object has been restored, it is necessary to create new one.

// Try to recover previously saved HighScore
HighScore = (HighScoreTable)highScoreEWD.Target;

// If nothing was recovered - create new
if (HighScore == null)
    HighScore = new HighScoreTable();

PersistHighScore() method is called after every change to highscore table. By setting the Target property of ExtendedWeakReference the saving mechanism is triggered and object is saved into flash memory.

public void PersistHighScore()
{
    // Persist HighScore by settinig the Target property
    // of ExtendedWeakReference
    highScoreEWD.Target = HighScore;
}

Tahoe emulator
Tetris running in Tahoe board emulator

Running the code

Game was developed and tested on Tahoe board from Embedded Fusion. Since there is no additional hardware required, it is possible to run game just in emulator. For those who don't own the Tahoe board, SDK with Tahoe emulator can be downloaded from Embedded Fusion support page.

The only one platform specific option in source code is in GPIOButtonInputProvider.cs file on lines 39-47. It is an button mapping for specific pins of the CPU. I'am using pins on Meridian CPU which are wired to Up, Down, Left, Right and Select button on Tahoe development kit.

ButtonPad[] buttons = new ButtonPad[]
{
    // Associate the buttons to the pins as setup in the emulator/hardware
    new ButtonPad(this, Button.Up    , Meridian.Pins.GPIO5),
    new ButtonPad(this, Button.Down  , Meridian.Pins.GPIO9),
    new ButtonPad(this, Button.Left  , Meridian.Pins.GPIO6),
    new ButtonPad(this, Button.Right , Meridian.Pins.GPIO6),
    new ButtonPad(this, Button.Select, Meridian.Pins.GPIO7),
};

Download

Micro Tetris source code is available for download MicroTetris.zip [102 kB].

Updated on 18th September 2008
Version for .NET Micro Framework 3.0 and Visual Studio 2008: MicroTetris3.zip [125 kB].