(blog 'zezin)


A Modular Tetris Library

To identify the correct boundaries among the software components is a hard task. A good modular and extensible structure is one of the features that identifies a healthy code base.

To show an example software reusability, this post will show you the architecture and the details of a simple and extensible Tetris game library.

Goals

First of all, the main requirement of this library is that it should be able to communicate with as many programming languages and environments as possible. To accomplish this, we have the following options:

  • Language binding: Also called wrapper code or glue code, it is the middle layer that allows the programming language to talk to each other in the same process. The software libraries are generally written in C and you must write the middle layer to enable this communication. Examples: Java Native Interface (JNI) for Java and ctypes for python.
  • Inter Process Communication: Mechanism that allows different and separate running processes to manage data. Examples: Sockets, message queues, RMI.

Between these choices, the C approach fits better our requirements because it will keep our code more simple and portable.

C Library

Writing stuff in C is painful. You always have to be careful about managing memory, dangling pointers and undefined behavior. But C is fast, has a simple design (kind of) and, together with C++, is the de facto language for libraries that must be shared with other languages.

Our C code will not have anything fancy. The good ol' C structs to model our data (board, blocks, color) and functions to change the data, like create_board(width, height), rotate(board), move_to_bottom(board).

For the unit tests, the C library greatest is used to automate our tests, so it will be safer to perform any future refactorings or improvements in the library.

Language Binding

The next sections will cover some techniques and tools related to the development of the language bindings.

C languages

It is pretty trivial to call our library functions with C-compatible languages (C, C++ and Objective-C) and no additional code is required. To use it, just compile all the files into a single executable or link the library as a shared library.

SWIG

Generally, writing the glue code by hand is error prone and complex and the final code tends to be hard to write. To automate this process, SWIG automagically generates the wrapper code of multiple programming languages. It currently supports Ruby, Python, Java, Javascript and many, many others.

SWIG, of course, is far from being a silver bullet. For example, this answer from Stackoverflow summarizes that one of the disadvantages is that the generated C code is ugly and may be tricky to set up a complex interface file. On the other hand, the advantages are the support for multiple languages and time savings when using simple functions.

Let me show a brief demo of a function that calculates the power of a number

int pow(int n) {
  return n * n;
}

To generate this glue code, we have to write an input interface file containing the desired functions to be exported. The first section is called the preamble and provides declarations to get the code to compile. The second declaration contains all the functions that you will be included in the wrapper code.

%module example
%{
  extern int pow(int n);
%}

extern int pow(int n);

To use this module in Python:

# Will create example.py and example_wrap.c
➜ swig -python example.i
➜ gcc -shared -fPIC example.c example_wrap.c \ 
  -I/usr/include/python2.7 -o _example.so
➜ python2
>>>> import example
>>>> example.power(3)
9

To use this module in Ruby:

# Content of extconf.rb
require 'mkmf'
create_makefile('example')
# In shell
➜ swig -ruby example.i
# This command will create the Makefile
➜ ruby extconf.rb
➜ make
➜ irb
irb(main):001:0> require './example'
irb(main):001:0> Example.power(2)
=> 9

Nice! We saw that with the special SWIG interface file, we can create the binding code easily into the supported languages and SWIG will handle all the particularities of each language binding. Check the SWIG interface file of the library if you are curious about it.

Web Browsers

SWIG is pretty useful but has one big downside. It is not possible to use it to target web browsers. To run our C code in them, we would have to rewrite the library in Javascript or use it with a plugin (Flash, NaCl).

However, there is a better alternative. With the Emscripten project, we can compile C and C++ code into Javascript that runs in the browsers without any external plugins.

Basically, this image show how this source-to-source compilation works:

asm_js.png

First, Emscripten calls clang to compile your C files and generate the LLVM bitcode from them. Then, the resulting bitcode is fed to the Emscripten LLVM backend called Fastcomp, which is translated to Javascript code. All these operations are wrapped into the emcc command, so all this process is transparent to the user.

The resulting Javascript is in the asm.js format, which is, in short, a strict subset of Javascript that is optimized for performance. For more information about it, check this post by John Resig.

The demo game was adapted from javascript-tetris and reuses its visual components, only replacing the Tetris game logic.

Everything else

If the programming language you are looking for is not yet supported by SWIG (Rust, Go or Nim), you will have to write the binding code by hand. ='(

Library Usage

After the wrapper code of your desired language is done, it is only a matter of calling the binding functions or methods and focus on the user interface of your game. If you want to learn more about SWIG, the game logic and the architecture, check other demo games in the github repository.