[gnome-games/quadrapassel-vala] quadrapassel: Port from C++ to Vala



commit f70ae959bb38d6bea2bca5435456f3f5334a115f
Author: Robert Ancell <robert ancell canonical com>
Date:   Wed Jan 4 16:03:56 2012 +1100

    quadrapassel: Port from C++ to Vala

 configure.ac                                       |   20 +-
 libgames-support/GnomeGamesSupport-1.0.vapi        |    8 +
 .../data/org.gnome.quadrapassel.gschema.xml.in     |   12 +-
 quadrapassel/src/Makefile.am                       |   54 +-
 quadrapassel/src/blockops.cpp                      |  894 --------------
 quadrapassel/src/blockops.h                        |  145 ---
 quadrapassel/src/blocks-cache.cpp                  |  379 ------
 quadrapassel/src/blocks-cache.h                    |   70 --
 quadrapassel/src/blocks.cpp                        |  398 ------
 quadrapassel/src/blocks.h                          |   73 --
 quadrapassel/src/config.vapi                       |    4 +
 quadrapassel/src/game-view.vala                    |  475 +++++++
 quadrapassel/src/game.vala                         |  558 +++++++++
 quadrapassel/src/highscores.cpp                    |   53 -
 quadrapassel/src/highscores.h                      |   41 -
 quadrapassel/src/main.cpp                          |   85 --
 quadrapassel/src/preview.cpp                       |  154 ---
 quadrapassel/src/preview.h                         |   66 -
 quadrapassel/src/preview.vala                      |  107 ++
 quadrapassel/src/quadrapassel.vala                 |  762 ++++++++++++
 quadrapassel/src/renderer.cpp                      |  296 -----
 quadrapassel/src/renderer.h                        |   64 -
 quadrapassel/src/scoreframe.cpp                    |  163 ---
 quadrapassel/src/scoreframe.h                      |   75 --
 quadrapassel/src/scoreframe.vala                   |  139 +++
 quadrapassel/src/sound.cpp                         |   61 -
 quadrapassel/src/sound.h                           |   32 -
 quadrapassel/src/sound.vala                        |   21 +
 quadrapassel/src/tetris.cpp                        | 1304 --------------------
 quadrapassel/src/tetris.h                          |  175 ---
 30 files changed, 2103 insertions(+), 4585 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 1b4e6f6..77f6aa8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -107,7 +107,6 @@ AC_SUBST([gamelist])
 
 # Feature matrix
 
-need_cxx=no
 need_vala=no
 need_rsvg=no
 need_sqlite=no
@@ -118,11 +117,7 @@ need_clutter=no
 
 for game in $gamelist; do
   case $game in
-    quadrapassel) need_cxx=yes ;;
-    *) ;;
-  esac
-  case $game in
-    glchess|gnomine|gnotravex|iagno|lightsoff|mahjongg) need_vala=yes ;;
+    glchess|gnomine|gnotravex|iagno|lightsoff|mahjongg|quadrapassel) need_vala=yes ;;
     *) ;;
   esac
   case $game in
@@ -189,16 +184,6 @@ if test "$need_vala" = "yes"; then
   AM_PROG_VALAC([0.13.0])
 fi
 
-if test "$need_cxx" = "yes"; then
-  AC_PROG_CXX
-
-  # Check whether a C++ was found (AC_PROG_CXX sets $CXX to "g++" even when it
-  # doesn't exist)
-  AC_LANG_PUSH([C++])
-  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],[],[AC_MSG_ERROR([No C++ compiler found])])
-  AC_LANG_POP([C++])
-fi
-
 AM_PROG_CC_C_O
 
 LT_INIT
@@ -206,7 +191,6 @@ LT_INIT
 GNOME_COMMON_INIT
 GNOME_DEBUG_CHECK
 GNOME_COMPILE_WARNINGS([maximum])
-GNOME_CXX_WARNINGS([yes])
 GNOME_MAINTAINER_MODE_DEFINES
 
 dnl ****************************************************************************
@@ -451,7 +435,6 @@ fi
 # ********
 
 AM_CFLAGS="$AM_CFLAGS $WARN_CFLAGS"
-AM_CXXFLAGS="$AM_CXXFLAGS $WARN_CXXFLAGS"
 
 # ****
 # i18n
@@ -552,7 +535,6 @@ GOBJECT_INTROSPECTION_CHECK([0.6.3])
 
 AC_SUBST([AM_CPPFLAGS])
 AC_SUBST([AM_CFLAGS])
-AC_SUBST([AM_CXXFLAGS])
 AC_SUBST([AM_LDFLAGS])
 
 ##############################################
diff --git a/libgames-support/GnomeGamesSupport-1.0.vapi b/libgames-support/GnomeGamesSupport-1.0.vapi
index 5f95ae1..778e5ac 100644
--- a/libgames-support/GnomeGamesSupport-1.0.vapi
+++ b/libgames-support/GnomeGamesSupport-1.0.vapi
@@ -192,5 +192,13 @@ namespace GnomeGamesSupport
         public unowned string? get_nth (int n);
         public Gtk.Widget create_widget (string selection, uint flags);
     }
+
+    [CCode (cheader_filename = "games-controls.h")]
+    public class ControlsList : Gtk.ScrolledWindow
+    {
+        public ControlsList (GLib.Settings settings);
+        public void add_control (string conf_key, string label, uint default_keyval);
+        public void add_controls (string first_conf_key, ...);
+    }
 }
 
diff --git a/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in b/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in
index 80adf4d..538e1e3 100644
--- a/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in
+++ b/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in
@@ -6,7 +6,7 @@
       <_description>Image to use for drawing blocks.</_description>
     </key>
     <key name="theme" type="s">
-      <default>'tangoshaded'</default>
+      <default>'clean'</default>
       <_summary>The theme used for rendering the blocks</_summary>
       <_description>The name of the theme used for rendering the blocks and the background.</_description>
     </key>
@@ -15,16 +15,6 @@
       <_summary>Level to start with</_summary>
       <_description>Level to start with.</_description>
     </key>
-    <key name="use-bg-image" type="b">
-      <default>true</default>
-      <_summary>Whether to use the background image</_summary>
-      <_description>This selects whether or not to draw the background image over the background color.</_description>
-    </key>
-    <key name="bg-color" type="s">
-      <default>'Black'</default>
-      <_summary>The background color</_summary>
-      <_description>The background color, in a format gdk_color_parse understands.</_description>
-    </key>
     <key name="do-preview" type="b">
       <default>true</default>
       <_summary>Whether to preview the next block</_summary>
diff --git a/quadrapassel/src/Makefile.am b/quadrapassel/src/Makefile.am
index f860056..74cec05 100644
--- a/quadrapassel/src/Makefile.am
+++ b/quadrapassel/src/Makefile.am
@@ -1,38 +1,38 @@
 bin_PROGRAMS = quadrapassel
 
 quadrapassel_SOURCES = \
-	main.cpp \
-	blocks.cpp \
-	blocks.h \
-	highscores.cpp \
-	highscores.h \
-	scoreframe.cpp \
-	scoreframe.h \
-	tetris.cpp \
-	tetris.h \
-	preview.cpp \
-	preview.h \
-	blockops.cpp \
-	blockops.h \
-	renderer.cpp \
-	renderer.h \
-	blocks-cache.cpp \
-	blocks-cache.h \
-	sound.cpp \
-	sound.h
+	config.vapi \
+	fixes.vapi \
+	scoreframe.vala \
+	quadrapassel.vala \
+	preview.vala \
+	game.vala \
+	game-view.vala \
+	sound.vala
 
-quadrapassel_CPPFLAGS = \
-	-I$(top_srcdir) \
-	$(AM_CPPFLAGS)
-
-quadrapassel_CXXFLAGS = \
+quadrapassel_CFLAGS = \
+	-I$(top_srcdir)/libgames-support \
+	-DVERSION=\"$(VERSION)\" \
+	-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
 	-DDATA_DIRECTORY=\"$(datadir)/quadrapassel\" \
 	-DSOUND_DIRECTORY=\"$(pkgdatadir)/sounds\" \
 	$(GTK_CFLAGS) \
 	$(CANBERRA_GTK_CFLAGS) \
 	$(CLUTTER_GTK_CFLAGS) \
-	$(CLUTTER_CFLAGS) \
-	$(AM_CXXFLAGS)
+	$(CLUTTER_CFLAGS)
+
+quadrapassel_VALAFLAGS = \
+	--pkg posix \
+	--pkg gtk+-3.0 \
+	--pkg pango \
+	--pkg pangocairo \
+	--pkg clutter-1.0 \
+	--pkg clutter-gtk-1.0 \
+	--pkg cogl-1.0 \
+	--pkg libcanberra \
+	--pkg libcanberra-gtk \
+	--vapidir $(top_srcdir)/libgames-support \
+	--pkg GnomeGamesSupport-1.0
 
 quadrapassel_LDADD = \
 	$(top_builddir)/libgames-support/libgames-support.la \
@@ -43,7 +43,7 @@ quadrapassel_LDADD = \
 	$(INTLLIBS)
 
 if HAVE_RSVG
-quadrapassel_CXXFLAGS += $(RSVG_CFLAGS) 
+quadrapassel_CFLAGS += $(RSVG_CFLAGS) 
 quadrapassel_LDADD += $(RSVG_LIBS)
 endif
 
diff --git a/quadrapassel/src/config.vapi b/quadrapassel/src/config.vapi
new file mode 100644
index 0000000..e90b1fe
--- /dev/null
+++ b/quadrapassel/src/config.vapi
@@ -0,0 +1,4 @@
+public const string VERSION;
+public const string GETTEXT_PACKAGE;
+public const string DATA_DIRECTORY;
+public const string SOUND_DIRECTORY;
diff --git a/quadrapassel/src/game-view.vala b/quadrapassel/src/game-view.vala
new file mode 100644
index 0000000..7c5fc7e
--- /dev/null
+++ b/quadrapassel/src/game-view.vala
@@ -0,0 +1,475 @@
+//FIXME: Drop animation
+//var timeline = new Clutter.Timeline (360);
+//b.actor.animate_with_timeline (Clutter.AnimationMode.EASE_IN_QUAD, timeline, "x", (float) x, "y", (float) y);
+//timeline.set_duration (360 / (5 - num_full_lines));
+//timeline.completed.connect (fall_completed_cb);
+
+private class BlockActor : Clutter.Clone
+{
+    public Block block;
+
+    public BlockActor (Block block, Clutter.Actor texture)
+    {
+        Object (source: texture);
+        this.block = block;
+    }
+
+    public void move (float x, float y)
+    {
+        animate (Clutter.AnimationMode.EASE_IN_QUAD, 60, "x", x, "y", y);
+    }
+
+    public void explode ()
+    {
+        raise_top ();
+        var timeline = new Clutter.Timeline (720);
+        timeline.completed.connect (explode_complete_cb);
+        animate_with_timeline (Clutter.AnimationMode.EASE_OUT_QUINT, timeline, "opacity", 0, "scale-x", 2f, "scale-y", 2f);
+    }
+    
+    private void explode_complete_cb ()
+    {
+        destroy ();
+    }
+}
+
+private class TextOverlay : Clutter.CairoTexture
+{
+    private string? _text = null;
+    public string text
+    {
+        get { return _text; }
+        set { _text = value; invalidate (); }
+    }
+
+    public TextOverlay (int width, int height)
+    {
+        auto_resize = true;
+        set_surface_size (width, height);
+    }
+
+    protected override bool draw (Cairo.Context cr)
+    {
+        clear ();
+
+        if (text == null)
+            return false;
+
+        // Center coordinates
+        uint w, h;
+        get_surface_size (out w, out h);
+        cr.translate (w / 2, h / 2);
+
+        var desc = Pango.FontDescription.from_string ("Sans");
+
+        var layout = Pango.cairo_create_layout (cr);
+        layout.set_text (text, -1);
+
+        var dummy_layout = layout.copy ();
+        dummy_layout.set_font_description (desc);
+        int lw, lh;
+        dummy_layout.get_size (out lw, out lh);
+
+        // desired height : lh = widget width * 0.9 : lw
+        desc.set_absolute_size (((float) lh / lw) * Pango.SCALE * w * 0.7);
+        layout.set_font_description (desc);
+
+        layout.get_size (out lw, out lh);
+        cr.move_to (-((double)lw / Pango.SCALE) / 2, -((double)lh / Pango.SCALE) / 2);
+        Pango.cairo_layout_path (cr, layout);
+        cr.set_source_rgb (0.333333333333, 0.341176470588, 0.32549019607);
+
+        /* A linewidth of 2 pixels at the default size. */
+        cr.set_line_width (width / 100.0);
+        cr.stroke_preserve ();
+
+        cr.set_source_rgb (1.0, 1.0, 1.0);
+        cr.fill ();
+
+        return false;
+    }
+}
+
+public class BlockTexture : Clutter.CairoTexture
+{
+    private int color;
+    private string? _theme = null;
+    public string? theme
+    {
+        get { return _theme; }
+        set
+        {
+            if (_theme == value)
+                return;
+            _theme = value;
+            invalidate ();
+        }
+    }
+    
+    public BlockTexture (int color, int size)
+    {
+        auto_resize = true;
+        set_surface_size (size, size);
+        this.color = color.clamp (0, 6);
+    }
+
+    protected override bool draw (Cairo.Context cr)
+    {
+        clear ();
+
+        uint w, h;
+        get_surface_size (out w, out h);      
+        cr.scale (w, h);
+
+        switch (theme)
+        {
+        default:
+        case "plain":
+            draw_plain (cr);
+            break;
+        case "clean":
+            draw_clean (cr);
+            break;
+        case "tangoflat":
+            draw_tango (cr, false);
+            break;
+        case "tangoshaded":
+            draw_tango (cr, true);
+            break;
+        }
+
+        return false;
+    }
+
+    private void draw_plain (Cairo.Context cr)
+    {
+        const double colors[32] =
+        {
+            1.0, 0.0, 0.0,
+            0.0, 1.0, 0.0,
+            0.0, 0.0, 1.0,
+            1.0, 1.0, 1.0,
+            1.0, 1.0, 0.0,
+            1.0, 0.0, 1.0,
+            0.0, 1.0, 1.0
+        };
+
+        cr.set_source_rgb(colors[color * 3], colors[color * 3 + 1], colors[color * 3 + 2]);
+        cr.paint ();
+    }
+
+    private void draw_rounded_rectangle (Cairo.Context cr, double x, double y, double w, double h, double r)
+    {
+        cr.move_to (x + r, y);
+        cr.line_to (x + w - r, y);
+        cr.curve_to (x + w - (r/2), y, x + w, y + r / 2, x + w, y + r);
+        cr.line_to (x + w, y + h - r);
+        cr.curve_to (x + w, y + h - r / 2, x + w - r / 2, y + h, x + w - r, y + h);
+        cr.line_to (x + r, y + h);
+        cr.curve_to (x + r / 2, y + h, x, y + h - r / 2, x, y + h - r);
+        cr.line_to (x, y + r);
+        cr.curve_to (x, y + r / 2, x + r / 2, y, x + r, y);
+    }
+
+    private void draw_clean (Cairo.Context cr)
+    {
+        /* The colors, first the lighter then the darker fill (for the gradient)
+           and then the stroke color  */
+        const double colors[72] =
+        {
+            0.780392156863, 0.247058823529, 0.247058823529,
+            0.713725490196, 0.192156862745, 0.192156862745,
+            0.61568627451, 0.164705882353, 0.164705882353, /* red */
+    
+            0.552941176471, 0.788235294118, 0.32549019607,
+            0.474509803922, 0.713725490196, 0.243137254902,
+            0.388235294118, 0.596078431373, 0.18431372549, /* green */
+    
+            0.313725490196, 0.450980392157, 0.623529411765,
+            0.239215686275, 0.345098039216, 0.474509803922,
+            0.21568627451, 0.313725490196, 0.435294117647, /* blue */
+    
+            1.0, 1.0, 1.0,
+            0.909803921569, 0.909803921569, 0.898039215686,
+            0.701960784314, 0.701960784314, 0.670588235294, /* white */
+    
+            0.945098039216, 0.878431372549, 0.321568627451,
+            0.929411764706, 0.839215686275, 0.113725490196,
+            0.760784313725, 0.682352941176, 0.0274509803922, /* yellow */
+    
+            0.576470588235, 0.364705882353, 0.607843137255,
+            0.443137254902, 0.282352941176, 0.46666666666,
+            0.439215686275, 0.266666666667, 0.46666666666, /* purple */
+    
+            0.890196078431, 0.572549019608, 0.258823529412,
+            0.803921568627, 0.450980392157, 0.101960784314,
+            0.690196078431, 0.388235294118, 0.0901960784314, /* orange */
+    
+            0.392156862745, 0.392156862745, 0.392156862745,
+            0.262745098039, 0.262745098039, 0.262745098039,
+            0.21568627451, 0.235294117647, 0.23921568627 /* grey */
+        };
+
+        /* Layout the block */
+        draw_rounded_rectangle (cr, 0.05, 0.05, 0.9, 0.9, 0.05);
+
+        /* Draw outline */
+        cr.set_source_rgb (colors[color * 9 + 6], colors[color * 9 + 7], colors[color * 9 + 8]);
+        cr.set_line_width (0.1);
+        cr.stroke_preserve ();
+
+        /* Fill with gradient */
+        var pat = new Cairo.Pattern.linear (0.35, 0, 0.55, 0.9);
+        pat.add_color_stop_rgb (0.0, colors[color * 9], colors[color * 9 + 1], colors[color * 9 + 2]);
+        pat.add_color_stop_rgb (1.0, colors[color * 9 + 3], colors[color * 9 + 4], colors[color * 9 + 5]);
+        cr.set_source (pat);
+        cr.fill ();
+    }
+
+    private void draw_tango (Cairo.Context cr, bool use_gradients)
+    {
+        /* the following garbage is derived from the official tango style guide */
+        const double colors[72] =
+        {
+            0.93725490196078431, 0.16078431372549021, 0.16078431372549021,
+            0.8, 0.0, 0.0,
+            0.64313725490196083, 0.0, 0.0, /* red */
+
+            0.54117647058823526, 0.88627450980392153, 0.20392156862745098,
+            0.45098039215686275, 0.82352941176470584, 0.086274509803921567,
+            0.30588235294117649, 0.60392156862745094, 0.023529411764705882, /* green */
+
+            0.44705882352941179, 0.62352941176470589, 0.81176470588235294,
+            0.20392156862745098, 0.396078431372549, 0.64313725490196083,
+            0.12549019607843137, 0.29019607843137257, 0.52941176470588236, /* blue */
+
+            0.93333333333333335, 0.93333333333333335, 0.92549019607843142,
+            0.82745098039215681, 0.84313725490196079, 0.81176470588235294,
+            0.72941176470588232, 0.74117647058823533, 0.71372549019607845, /* white */
+
+            0.9882352941176471, 0.9137254901960784, 0.30980392156862746,
+            0.92941176470588238, 0.83137254901960789, 0.0,
+            0.7686274509803922, 0.62745098039215685, 0.0, /* yellow */
+
+            0.67843137254901964, 0.49803921568627452, 0.6588235294117647,
+            0.45882352941176469, 0.31372549019607843, 0.4823529411764706,
+            0.36078431372549019, 0.20784313725490197, 0.4, /* purple */
+
+            0.9882352941176471, 0.68627450980392157, 0.24313725490196078,
+            0.96078431372549022, 0.47450980392156861, 0.0,
+            0.80784313725490198, 0.36078431372549019, 0.0, /* orange (replacing cyan) */
+
+            0.33, 0.34, 0.32,
+            0.18, 0.2, 0.21,
+            0.10, 0.12, 0.13 /* grey */
+        };
+
+        if (use_gradients)
+        {
+             var pat = new Cairo.Pattern.linear (0.35, 0, 0.55, 0.9);
+             pat.add_color_stop_rgb (0.0, colors[color * 9], colors[color * 9 + 1], colors[color * 9 + 2]);
+             pat.add_color_stop_rgb (1.0, colors[color * 9 + 3], colors[color * 9 + 4], colors[color * 9 + 5]);
+             cr.set_source (pat);
+        }
+        else
+             cr.set_source_rgb (colors[color * 9], colors[color * 9 + 1], colors[color * 9 + 2]);
+
+        draw_rounded_rectangle (cr, 0.05, 0.05, 0.9, 0.9, 0.2);
+        cr.fill_preserve ();  /* fill with shaded gradient */
+
+        cr.set_source_rgb (colors[color * 9 + 6], colors[color * 9 + 7], colors[color * 9 + 8]);
+
+        cr.set_line_width (0.1);
+        cr.stroke ();  /* add darker outline */
+
+        draw_rounded_rectangle (cr, 0.15, 0.15, 0.7, 0.7, 0.08);
+        if (use_gradients)
+        {
+            var pat = new Cairo.Pattern.linear (-0.3, -0.3, 0.8, 0.8);
+            /* yellow and white blocks need a brighter highlight */
+            switch (color)
+            {
+            case 3:
+            case 4:
+                pat.add_color_stop_rgba (0.0, 1.0, 1.0, 1.0, 1.0);
+                pat.add_color_stop_rgba (1.0, 1.0, 1.0, 1.0, 0.0);
+                break;
+            default:
+                pat.add_color_stop_rgba (0.0, 0.9295, 0.9295, 0.9295, 1.0);
+                pat.add_color_stop_rgba (1.0, 0.9295, 0.9295, 0.9295, 0.0);
+                break;
+            }
+            cr.set_source (pat);
+        }
+        else
+            cr.set_source_rgba (1.0, 1.0, 1.0, 0.35);
+        cr.stroke ();  /* add inner edge highlight */
+    }   
+}
+
+public class GameView : GtkClutter.Embed
+{
+    /* Game being shown */
+    private Game game;
+
+    private Clutter.Group playing_field;
+
+    /* Overlay to draw messages on */
+    private TextOverlay text_overlay;
+
+    /* Textures used to draw blocks */
+    private BlockTexture[] block_textures;
+
+    /* Blocks */
+    private HashTable<Block, BlockActor> blocks;
+
+    // FIXME: Remove
+    private bool show_pause;
+    private bool show_game_over;
+   
+    private int num_full_lines;
+
+    private int cell_size
+    {
+        get { return int.min (get_allocated_width () / COLUMNS, get_allocated_height () / LINES); }
+    }
+
+    public string theme
+    {
+        set
+        {
+            foreach (var texture in block_textures)
+                texture.theme = value;
+        }
+    }
+
+    public GameView (Game game)
+    {
+        blocks = new HashTable<Block, BlockActor> (direct_hash, direct_equal);
+
+        this.game = game;
+        game.block_added.connect (block_added_cb);
+        game.block_moved.connect (block_moved_cb);
+        game.block_destroyed.connect (block_destroyed_cb);
+
+        size_allocate.connect (size_allocate_cb);
+
+        set_size_request (COLUMNS * 190 / LINES, 190);
+
+        var stage = (Clutter.Stage) get_stage ();
+        Clutter.Color stage_color = { 0x0, 0x0, 0x0, 0xff };
+        stage.set_color (stage_color);
+
+        playing_field = new Clutter.Group ();
+        stage.add_actor (playing_field);
+
+        block_textures = new BlockTexture[NCOLORS];
+        for (var i = 0; i < block_textures.length; i++)
+        {
+            // FIXME: Have to set a size to avoid an assertion in Clutter
+            block_textures[i] = new BlockTexture (i, 1);
+            block_textures[i].hide ();
+            playing_field.add_actor (block_textures[i]);
+        }
+    }
+
+    private void block_added_cb (Block block)
+    {
+        var actor = new BlockActor (block, block_textures[block.color]);
+        actor.set_size (cell_size, cell_size);
+        actor.set_position (block.x * cell_size, block.y * cell_size);
+        playing_field.add_actor (actor);
+        blocks.insert (block, actor);
+    }
+
+    private void block_moved_cb (Block block, bool animate)
+    {
+        var actor = blocks.lookup (block);
+        if (animate)
+            actor.move (block.x * cell_size, block.y * cell_size);
+        else
+            actor.set_position (block.x * cell_size, block.y * cell_size);
+    }
+
+    private void block_destroyed_cb (Block block)
+    {
+        var actor = blocks.lookup (block);
+        actor.explode ();
+    }
+
+    private void fall_completed_cb (Clutter.Timeline timeline)
+    {
+        //After fall, start the earthquake effect
+        float x, y;
+        playing_field.get_position (out x, out y);
+        playing_field.set_position (x, y + cell_size * num_full_lines * 0.25f);
+        playing_field.animate (Clutter.AnimationMode.EASE_OUT_BOUNCE, 720 / (5 - num_full_lines), "x", x, "y", y);
+    }
+
+    public void size_allocate_cb (Gtk.Widget widget, Gtk.Allocation allocation)
+    {
+        var stage = (Clutter.Stage) get_stage ();
+
+        foreach (var texture in block_textures)
+            texture.set_size (cell_size, cell_size);
+
+        var iter = HashTableIter<Block, BlockActor> (blocks);
+        while (true)
+        {
+            Block block;
+            BlockActor actor;
+            if (!iter.next (out block, out actor))
+                break;
+            actor.set_position (block.x * cell_size, block.y * cell_size);
+        }
+
+        if (text_overlay != null)
+            text_overlay.set_size (get_allocated_width (), get_allocated_height ());
+        else
+        {
+            text_overlay = new TextOverlay (get_allocated_width (), get_allocated_height ());
+            stage.add (text_overlay);
+        }
+
+        draw_message ();
+
+        text_overlay.raise_top ();
+        var x = (int) ((get_allocated_width () - cell_size * COLUMNS) / 2);
+        var y = (int) ((get_allocated_height () - cell_size * LINES) / 2);
+        playing_field.set_position (x, y);
+    }
+
+    public void draw_message ()
+    {
+        if (show_pause)
+            text_overlay.text = _("Paused");
+        else if (show_game_over)
+            text_overlay.text = _("Game Over");
+        else
+            text_overlay.text = null;
+    }
+
+    public void show_pause_message ()
+    {
+        show_pause = true;
+        draw_message ();
+    }
+
+    public void hide_pause_message ()
+    {
+        show_pause = false;
+        draw_message ();
+    }
+
+    public void show_game_over_message ()
+    {
+        show_game_over = true;
+        draw_message ();
+    }
+
+    public void hide_game_over_message ()
+    {
+        show_game_over = false;
+        draw_message ();
+    }
+}
diff --git a/quadrapassel/src/game.vala b/quadrapassel/src/game.vala
new file mode 100644
index 0000000..bd1b35e
--- /dev/null
+++ b/quadrapassel/src/game.vala
@@ -0,0 +1,558 @@
+// FIXME: Bad globals
+int LINES = 20;
+int COLUMNS = 14;
+
+int block_rotation_next;
+int block_color_next;
+
+const int NCOLORS = 7;
+
+const int block_table[448] =
+{
+    /* *** */
+    /* *   */
+    0, 0, 0, 0,
+    1, 1, 1, 0,
+    1, 0, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 0, 0, 0,
+
+    0, 0, 1, 0,
+    1, 1, 1, 0,
+    0, 0, 0, 0,
+    0, 0, 0, 0,
+
+    1, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    /* *** */
+    /*   * */
+    0, 0, 0, 0,
+    1, 1, 1, 0,
+    0, 0, 1, 0,
+    0, 0, 0, 0,
+
+    0, 1, 1, 0,
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    1, 0, 0, 0,
+    1, 1, 1, 0,
+    0, 0, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    1, 1, 0, 0,
+    0, 0, 0, 0,
+
+    /* *** */
+    /*  *  */
+    0, 0, 0, 0,
+    1, 1, 1, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    1, 1, 1, 0,
+    0, 0, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    1, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    /*  ** */
+    /* **  */
+
+    0, 0, 0, 0,
+    0, 1, 1, 0,
+    1, 1, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 0, 1, 0,
+    0, 0, 0, 0,
+
+    0, 0, 0, 0,
+    0, 1, 1, 0,
+    1, 1, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 0, 1, 0,
+    0, 0, 0, 0,
+          
+    /* **  */
+    /*  ** */
+
+    0, 0, 0, 0,
+    1, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 0, 0, 0,
+
+    0, 0, 1, 0,
+    0, 1, 1, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    0, 0, 0, 0,
+    1, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 0, 0, 0,
+
+    0, 0, 1, 0,
+    0, 1, 1, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    /* **** */
+    0, 0, 0, 0,
+    1, 1, 1, 1,
+    0, 0, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+
+    0, 0, 0, 0,
+    1, 1, 1, 1,
+    0, 0, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+    0, 1, 0, 0,
+
+    /* ** */
+    /* ** */
+    0, 0, 0, 0,
+    0, 1, 1, 0,
+    0, 1, 1, 0,
+    0, 0, 0, 0,
+
+    0, 0, 0, 0,
+    0, 1, 1, 0,
+    0, 1, 1, 0,
+    0, 0, 0, 0,
+
+    0, 0, 0, 0,
+    0, 1, 1, 0,
+    0, 1, 1, 0,
+    0, 0, 0, 0,
+
+    0, 0, 0, 0,
+    0, 1, 1, 0,
+    0, 1, 1, 0,
+    0, 0, 0, 0
+};
+
+public class Block
+{
+    public int x;
+    public int y;
+    public int color;
+}
+
+public class Game
+{
+    private Block[,] blocks;
+    private List<Block> shape = null;
+
+    /* Position of current block */
+    private int block_x;
+    private int block_y;
+
+    private int block_color;
+   
+    /* Rotation of block */
+    private int block_rotation;
+
+    /* Timer to animate */
+    private uint drop_timeout = 0;
+
+    public signal void block_added (Block block);
+    public signal void block_moved (Block block, bool animate);
+    public signal void block_destroyed (Block block);
+
+    public Game (int lines = 20, int columns = 14)
+    {
+        blocks = new Block[columns, lines];
+    }
+    
+    public void start (int filled_lines = 0, int fill_prob = 5)
+    {
+        /* Start with some pre-filled spaces */
+        for (var y = 0; y < LINES; y++)
+        {
+            /* Pick at least one column to be empty */
+            var blank = Random.int_range (0, COLUMNS);
+
+            for (var x = 0; x < COLUMNS; x++)
+            {
+                if (y >= (LINES - filled_lines) && x != blank && Random.int_range (0, 10) < fill_prob)
+                {
+                    blocks[x, y] = new Block ();
+                    blocks[x, y].color = Random.int_range (0, NCOLORS);
+                }
+                else
+                    blocks[x, y] = null;
+            }
+        }
+
+        pick_next_shape ();
+        add_shape ();
+
+        set_timestep (1);
+    }
+
+    public bool move_left ()
+    {
+        return move_shape (-1, 0, 0);
+    }
+
+    public bool move_right ()
+    {
+        return move_shape (1, 0, 0);
+    }
+
+    public bool rotate_left ()
+    {
+        return move_shape (0, 0, -1);
+    }
+
+    public bool rotate_right ()
+    {
+        return move_shape (0, 0, 1);
+    }
+
+    public void fast_forward (bool enable)
+    {
+        if (enable)
+            set_timestep (-1);
+        else
+            set_timestep (1);
+    }
+
+    public void drop ()
+    {
+        while (move_shape (0, 1, 0));
+        fall_timeout_cb ();
+    }
+
+    private void set_timestep (int level)
+    {
+        var timestep = 0;
+        if (level < 0)
+            timestep = 40;
+        else
+            timestep = (int) Math.round (80 + 800.0 * Math.pow (0.75, level - 1));
+        timestep = int.max (10, timestep);
+
+        if (drop_timeout != 0)
+            Source.remove (drop_timeout);
+        drop_timeout = Timeout.add (timestep, fall_timeout_cb);
+    }
+
+    private bool fall_timeout_cb ()
+    {
+        /* Drop the shape down, and create a new one when it can't move */
+        if (!move_shape (0, 1, 0))
+        {
+            destroy_lines ();
+            add_shape ();
+        }
+
+        return true;
+    }
+
+    private void add_shape ()
+    {
+        block_rotation = block_rotation_next;
+        block_color = block_color_next;
+
+        pick_next_shape ();
+
+        /* Place this block at top of the field */
+        var offset = block_color * 64 + block_rotation * 16;
+        shape = null;
+        var min_width = 4, max_width = 0, min_height = 4, max_height = 0;
+        for (var x = 0; x < 4; x++)
+        {
+            for (var y = 0; y < 4; y++)
+            {
+                if (block_table[offset + y * 4 + x] != 0)
+                {
+                    min_width = int.min (x, min_width);
+                    max_width = int.max (x + 1, max_width);
+                    min_height = int.min (y, min_height);
+                    max_height = int.max (y + 1, max_height);
+                    var b = new Block ();
+                    b.color = block_color;
+                    b.x = x;
+                    b.y = y;
+                    shape.append (b);
+                }
+            }
+        }
+        block_x = (COLUMNS - max_width) / 2;
+        block_y = -min_height;
+        foreach (var b in shape)
+        {
+            b.x += block_x;
+            b.y += block_y;
+
+            /* Abort if can't place there */
+            if (blocks[b.x, b.y] != null)
+            {
+                shape = null;
+                return;
+            }
+        }
+
+        foreach (var b in shape)
+        {
+            blocks[b.x, b.y] = b;
+            block_added (b);
+        }
+    }
+
+    private void pick_next_shape ()
+    {
+        /*if (difficult_blocks)
+        {
+            bastard_pick ();
+            block_color_next = -1;
+        }
+        else*/
+
+        block_rotation_next = Random.int_range (0, 4);
+        block_color_next = Random.int_range (0, NCOLORS);
+    }
+
+    private void destroy_lines ()
+    {
+        var fall_distance = 0;
+        for (var y = blocks.length[1] - 1; y >= 0; y--)
+        {
+            var explode = true;
+            for (var x = 0; x < blocks.length[0]; x++)
+            {
+                if (blocks[x, y] == null)
+                {
+                    explode = false;
+                    break;
+                }
+            }
+
+            /* Either destroy this line or drop it down */
+            if (explode)
+            {
+                fall_distance++;
+                for (var x = 0; x < blocks.length[0]; x++)
+                {
+                    block_destroyed (blocks[x, y]);
+                    blocks[x, y] = null;
+                }
+            }
+            else if (fall_distance > 0)
+            {
+                for (var x = 0; x < blocks.length[0]; x++)
+                {
+                    var b = blocks[x, y];
+                    if (b != null)
+                    {
+                        b.y += fall_distance;
+                        blocks[b.x, b.y] = b;
+                        blocks[x, y] = null;
+                        block_moved (b, true);
+                    }
+                }
+            }
+        }
+    }
+
+#if 0
+    /*
+     * An implementation of "Bastard" algorithm
+     * it comes from Federico Poloni's "bastet"
+     */
+    private void bastard_pick ()
+    {
+        var scores = new int[NCOLORS];
+        var blocks = new int[NCOLORS];
+        var chance = new int[NCOLORS];
+
+        animate = false;
+        /* This generates a priority for each block */
+        save_field ();
+        for (block_index = 0; block_index < NCOLORS; block_index++)
+        {
+            scores[block_index] = -32000;
+            for (block_rotation = 0; block_rotation < 4; block_rotation++)
+            {
+                for (block_x = 0; block_x < COLUMNS; block_x++)
+                {
+                    int this_score = 0;
+                    int x, y;
+
+                    if (!block_ok_here (block_x, block_y = 0, block_index, block_rotation))
+                        continue;
+
+                    drop_block ();
+
+                    /* Count the completed lines */
+                    for (y = int.min (block_y + 4, LINES); y > 0; --y)
+                        if (check_full_line (y))
+                            this_score += 5000;
+
+                    /* Count heights of columns */
+                    for (x = 0; x < COLUMNS; x++)
+                    {
+                        for (y = 0; y < LINES; y++)
+                            if (blocks[x, y].what == SlotType.LAYING)
+                                break;
+                        this_score -= 5 * (LINES - y);
+                    }
+
+                    restore_field ();
+                    if (scores[block_index] < this_score)
+                        scores[block_index] = this_score;
+                }
+            }
+        }
+
+        for (var i = 0; i < NCOLORS; i++)
+        {
+            /* Initialize chances table */
+            chance[i] = 100;
+            /* Initialize block/priority table */
+            blocks[i] = i;
+            /* Perturb score (-2 to +2), to avoid stupid tie handling */
+            scores[i] += Random.int_range (-2, 2);
+        }
+
+        /* Sorts blocks by priorities, worst (interesting to us) first*/
+        for (var i = 0; i < NCOLORS; i++)
+        {
+            for (var ii = 0; ii < NCOLORS - 1; ii++)
+            {
+                if (scores[blocks[ii]] > scores[blocks[ii+1]])
+                {
+                    int t = blocks[ii];
+                    blocks[ii] = blocks[ii+1];
+                    blocks[ii+1] = t;
+                }
+            }
+        }
+
+        /* Lower the chances we're giving the worst one */
+        chance[0] = 75;
+        chance[1] = 92;
+        chance[2] = 98;
+
+        /* Actually choose a piece */
+        int rnd = Random.int_range (0, 99);
+        for (var i = 0; i < NCOLORS; i++)
+        {
+            block_index = blocks[i];
+            if (rnd < chance[i])
+                break;
+        }
+
+        /* This will almost certainly not given next */
+        animate = true;
+    }
+#endif
+
+    private bool move_shape (int x_step, int y_step, int r_step)
+    {
+        /* Lift up block and rotate it */
+        foreach (var b in shape)
+            blocks[b.x, b.y] = null;
+        rotate_shape (r_step);
+
+        /* Check it can fit into the new location */
+        var can_move = true;
+        foreach (var b in shape)
+        {
+            var x = b.x + x_step;
+            var y = b.y + y_step;
+            if (x < 0 || x >= COLUMNS || y >= LINES || blocks[x, y] != null)
+            {
+                can_move = false;
+                break;
+            }
+        }
+
+        /* Place in the new location or put it back where it was */
+        if (can_move)
+        {
+            foreach (var b in shape)
+            {
+                b.x += x_step;
+                b.y += y_step;
+                blocks[b.x, b.y] = b;
+                var animate = x_step != 0 || y_step != 0;
+                block_moved (b, animate);
+            }
+            block_x += x_step;
+            block_y += y_step;
+        }
+        else
+        {
+            rotate_shape (-r_step);
+            foreach (var b in shape)
+                blocks[b.x, b.y] = b;
+        }
+
+        return can_move;
+    }
+
+    private void rotate_shape (int r_step)
+    {
+        var r = block_rotation + r_step;
+        if (r < 0)
+            r += 4;
+        if (r >= 4)
+            r -= 4;
+
+        if (r == block_rotation)
+            return;
+        block_rotation = r;
+
+        /* Rearrange current blocks */
+        unowned List<Block> b = shape;
+        var offset = block_color * 64 + r * 16;
+        for (var x = 0; x < 4; x++)
+        {
+            for (var y = 0; y < 4; y++)
+            {
+                if (block_table[offset + y * 4 + x] != 0)
+                {
+                    b.data.x = block_x + x;
+                    b.data.y = block_y + y;
+                    b = b.next;
+                }
+            }
+        }
+    }
+}
diff --git a/quadrapassel/src/preview.vala b/quadrapassel/src/preview.vala
new file mode 100644
index 0000000..992e7b3
--- /dev/null
+++ b/quadrapassel/src/preview.vala
@@ -0,0 +1,107 @@
+public class Preview : GtkClutter.Embed
+{
+    /* Textures used to draw blocks */
+    private BlockTexture[] block_textures;
+
+    /* Blocks being shown */
+    private const int PREVIEW_WIDTH = 4;
+    private const int PREVIEW_HEIGHT = 4;
+    private Clutter.Clone[,] blocks;
+
+    /* The block being previewed */
+    private int blocknr = -1;
+
+    /* The color of the block */
+    private int color;
+
+    /* Clutter representation of a piece */
+    private Clutter.Group piece;
+
+    public string theme
+    {
+        set
+        {
+            foreach (var texture in block_textures)
+                texture.theme = value;
+            set_block (blocknr, color, true);
+        }
+    }
+
+    private int cell_size
+    {
+        get { return (get_allocated_width () + get_allocated_height ()) / 2 / 5; }
+    }
+
+    public Preview ()
+    {
+        blocks = new Clutter.Clone[PREVIEW_WIDTH, PREVIEW_HEIGHT];
+
+        size_allocate.connect (size_allocate_cb);
+
+        /* FIXME: We should scale with the rest of the UI, but that requires
+         * changes to the widget layout - i.e. wrap the preview in an
+         * fixed-aspect box. */
+        set_size_request (120, 120);
+        var stage = (Clutter.Stage) get_stage ();
+
+        Clutter.Color stage_color = { 0x0, 0x0, 0x0, 0xff };
+        stage.set_color (stage_color);
+        piece = new Clutter.Group ();
+        stage.add (piece);
+
+        block_textures = new BlockTexture[NCOLORS];
+        for (var i = 0; i < block_textures.length; i++)
+        {
+            // FIXME: Have to set a size to avoid an assertion in Clutter
+            block_textures[i] = new BlockTexture (i, 1);
+            block_textures[i].hide ();
+            piece.add_actor (block_textures[i]);
+        }
+    }
+
+    public void set_block (int bnr, int bcol, bool force)
+    {
+        blocknr = bnr;
+        color = bcol;
+
+        // FIXME: Move logic outside this class
+        var disable = false;
+        if (!force && (!do_preview || difficult_blocks))
+            disable = true;
+
+        var min_width = 4, max_width = 0, min_height = 4, max_height = 0;
+        for (var x = 0; x < PREVIEW_WIDTH; x++)
+        {
+            for (var y = 0; y < PREVIEW_HEIGHT; y++)
+            {
+                if (blocks[x, y] != null)
+                    blocks[x, y].destroy ();
+                blocks[x, y] = null;
+                if (!disable && blocknr != -1 && block_table[blocknr * 64 + block_rotation_next * 16 + y * 4 + x] != 0)
+                {
+                    min_width = int.min (x, min_width);
+                    max_width = int.max (x + 1, max_width);
+                    min_height = int.min (y, min_height);
+                    max_height = int.max (y + 1, max_height);
+
+                    blocks[x, y] = new Clutter.Clone (block_textures[color]);
+                    blocks[x, y].set_size (cell_size, cell_size);
+                    blocks[x, y].set_position (x * cell_size, y * cell_size);
+                    piece.add_actor (blocks[x, y]);
+                }
+            }
+        }
+
+        piece.set_anchor_point ((min_width + max_width) * 0.5f * cell_size, (min_height + max_height) * 0.5f * cell_size);
+        piece.set_position (get_allocated_width () / 2, get_allocated_height () / 2);
+        piece.set_scale (0.6, 0.6);
+        piece.animate (Clutter.AnimationMode.EASE_IN_OUT_SINE, 180, "scale-x", 1.0, "scale-y", 1.0);
+    }
+
+    private void size_allocate_cb (Gtk.Allocation allocation)
+    {
+        foreach (var texture in block_textures)
+            texture.set_size (cell_size, cell_size);
+        set_block (blocknr, color, true);
+    }
+}
diff --git a/quadrapassel/src/quadrapassel.vala b/quadrapassel/src/quadrapassel.vala
new file mode 100644
index 0000000..cd370fe
--- /dev/null
+++ b/quadrapassel/src/quadrapassel.vala
@@ -0,0 +1,762 @@
+bool difficult_blocks = false;
+bool do_preview = true;
+
+public class Quadrapassel
+{
+    /* Application settings */
+    private Settings settings;
+
+    /* Main window */
+    private Gtk.Window w;
+
+    /* Game being played */
+    private Game game;
+
+    /* Rendering of game */
+    private GameView view;
+
+    /* Preview of the next shape */
+    private Preview preview;
+
+    private ScoreFrame score_frame;
+    private GnomeGamesSupport.Scores high_scores;
+
+    private bool paused;
+    private bool one_pause;
+
+    private bool in_play;
+
+    private const int TILE_THRESHOLD = 65;
+
+    private const int DEFAULT_WIDTH = 500;
+    private const int DEFAULT_HEIGHT = 550;
+
+    private Gtk.Dialog preferences_dialog;
+    private Gtk.SpinButton starting_level_spin;
+    private Preview theme_preview;
+    private int starting_level;
+    private static int cmdline_level = 0;
+
+    private int line_fill_height;
+    private int line_fill_prob;
+
+    private Gtk.SpinButton fill_height_spinner;
+    private Gtk.SpinButton fill_prob_spinner;
+    private Gtk.CheckButton do_preview_toggle;
+    private Gtk.CheckButton difficult_blocks_toggle;
+    private Gtk.CheckButton rotate_counter_clock_wise_toggle;
+    private Gtk.CheckButton use_target_toggle;
+    private Gtk.CheckButton sound_toggle;
+
+    private int move_left;
+    private int move_right;
+    private int move_down;
+    private int move_drop;
+    private int move_rotate;
+    private int move_pause;
+
+    private Gtk.Action new_game_action;
+    private GnomeGamesSupport.PauseAction pause_action;
+    private Gtk.Action scores_action;
+    private Gtk.Action preferences_action;
+
+    private bool fast_fall;
+
+    private const Gtk.ActionEntry actions[] =
+    {
+        { "GameMenu", null, N_("_Game") },
+        { "SettingsMenu", null, N_("_Settings") },
+        { "HelpMenu", null, N_("_Help") },
+        { "NewGame", GnomeGamesSupport.STOCK_NEW_GAME, null, null, null, new_game_cb },
+        { "Scores", GnomeGamesSupport.STOCK_SCORES, null, null, null, scores_cb },
+        { "Quit", Gtk.Stock.QUIT, null, null, null, quit_cb },
+        { "Preferences", Gtk.Stock.PREFERENCES, null, null, null, preferences_cb },
+        { "Contents", GnomeGamesSupport.STOCK_CONTENTS, null, null, null, help_cb },
+        { "About", Gtk.Stock.ABOUT, null, null, null, about_cb }
+    };
+
+    public Quadrapassel (int cmd_level)
+    {
+        var ui_description =
+        "<ui>" +
+        "  <menubar name='MainMenu'>" +
+        "    <menu action='GameMenu'>" +
+        "      <menuitem action='NewGame'/>" +
+        "      <menuitem action='_pause'/>" +
+        "      <separator/>" +
+        "      <menuitem action='Scores'/>" +
+        "      <separator/>" +
+        "      <menuitem action='Quit'/>" +
+        "    </menu>" +
+        "    <menu action='SettingsMenu'>" +
+        "      <menuitem action='Preferences'/>" +
+        "    </menu>" +
+        "    <menu action='HelpMenu'>" +
+        "      <menuitem action='Contents'/>" +
+        "      <menuitem action='About'/>" +
+        "    </menu>" +
+        "  </menubar>" +
+        "</ui>";
+
+        settings = new Settings ("org.gnome.quadrapassel");
+
+        w = new Gtk.Window (Gtk.WindowType.TOPLEVEL);
+        w.set_title (_("Quadrapassel"));
+
+        w.delete_event.connect (window_delete_event_cb);
+
+        line_fill_height = 0;
+        line_fill_prob = 5;
+
+        w.set_default_size (DEFAULT_WIDTH, DEFAULT_HEIGHT);
+        //games_conf_add_window (w, KEY_SAVED_GROUP);
+
+        game = new Game ();
+
+        preview = new Preview ();
+        view = new GameView (game);
+
+        init_options ();
+
+        /* prepare menus */
+        GnomeGamesSupport.stock_init ();
+        var action_group = new Gtk.ActionGroup ("MenuActions");
+        action_group.set_translation_domain (GETTEXT_PACKAGE);
+        action_group.add_actions (actions, this);
+        var ui_manager = new Gtk.UIManager ();
+        ui_manager.insert_action_group (action_group, 0);
+        try
+        {
+            ui_manager.add_ui_from_string (ui_description, -1);
+        }
+        catch (Error e)
+        {
+        }
+        w.add_accel_group (ui_manager.get_accel_group ());
+
+        new_game_action = action_group.get_action ("NewGame");
+        pause_action = new GnomeGamesSupport.PauseAction ("_pause");
+        pause_action.state_changed.connect (pause_cb);
+        action_group.add_action_with_accel (pause_action, null);
+        scores_action = action_group.get_action ("Scores");
+        preferences_action = action_group.get_action ("Preferences");
+
+        var menubar = ui_manager.get_widget ("/MainMenu");
+
+        var hb = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+
+        var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        w.add (vbox);
+        vbox.pack_start (menubar, false, false, 0);
+        vbox.pack_start (hb, true, true, 0);
+
+        var aspect_frame = new Gtk.AspectFrame ("", 0.5f, 0.5f, (float) COLUMNS / (float) LINES, false);
+        aspect_frame.set_shadow_type (Gtk.ShadowType.NONE);
+        aspect_frame.add (view);
+
+        w.set_events (w.get_events () | Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK);
+
+        var vb1 = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        vb1.set_border_width (10);
+        vb1.pack_start (aspect_frame, true, true, 0);
+        hb.pack_start (vb1, true, true, 0);
+
+        w.key_press_event.connect (key_press_event_cb);
+        w.key_release_event.connect (key_release_event_cb);
+
+        var vb2 = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        vb2.set_border_width (10);
+        hb.pack_end (vb2, false, false, 0);
+
+        vb2.pack_start (preview, false, false, 0);
+
+        if (cmdline_level <= 0)
+            cmdline_level = settings.get_int ("starting-level");
+        score_frame = new ScoreFrame (cmdline_level);
+
+        vb2.pack_end (score_frame.get_widget (), true, false, 0);
+        high_scores = new GnomeGamesSupport.Scores ("quadrapassel",
+                                                    new GnomeGamesSupport.ScoresCategory[0],
+                                                    null,
+                                                    null,
+                                                    0,
+                                                    GnomeGamesSupport.ScoreStyle.PLAIN_DESCENDING);
+
+        score_frame.set_level (starting_level);
+        score_frame.set_starting_level (starting_level);
+
+        pause_action.set_sensitive (false);
+        preferences_action.set_sensitive (true);
+    }
+
+    public void show ()
+    {
+        w.show_all ();
+    }
+
+    private void preferences_dialog_close_cb (Gtk.Widget widget)
+    {
+        preferences_dialog.destroy ();
+        preferences_dialog = null;
+        new_game_action.sensitive = true;
+    }
+
+    private void preferences_dialog_response_cb (Gtk.Widget dialog, int response_id)
+    {
+        preferences_dialog_close_cb (preferences_dialog);
+    }
+
+    private void init_options ()
+    {
+        view.theme = settings.get_string ("theme");
+        preview.theme = settings.get_string ("theme");
+
+        starting_level = settings.get_int ("starting-level");
+        if (starting_level < 1)
+            starting_level = 1;
+        if (starting_level > 20)
+            starting_level = 20;
+
+        sound_enable (settings.get_boolean ("sound"));
+
+        do_preview = settings.get_boolean ("do-preview");
+
+        difficult_blocks = settings.get_boolean ("pick-difficult-blocks");
+
+        line_fill_height = settings.get_int ("line-fill-height");
+        if (line_fill_height < 0)
+            line_fill_height = 0;
+        if (line_fill_height > 19)
+            line_fill_height = 19;
+
+        line_fill_prob = settings.get_int ("line-fill-probability");
+        if (line_fill_prob < 0)
+            line_fill_prob = 0;
+        if (line_fill_prob > 10)
+            line_fill_prob = 10;
+
+        move_left = settings.get_int ("key-left");
+        move_right = settings.get_int ("key-right");
+        move_down = settings.get_int ("key-down");
+        move_drop = settings.get_int ("key-drop");
+        move_rotate = settings.get_int ("key-rotate");
+        move_pause = settings.get_int ("key-pause");
+    }
+
+    private void preferences_cb (Gtk.Action action)
+    {
+        if (preferences_dialog != null)
+        {
+            preferences_dialog.present ();
+            return;
+        }
+
+        /* create the dialog */
+        preferences_dialog = new Gtk.Dialog.with_buttons (_("Quadrapassel Preferences"), w, (Gtk.DialogFlags)0, Gtk.Stock.CLOSE, Gtk.ResponseType.CLOSE, null);
+        preferences_dialog.set_border_width (5);
+        var vbox = (Gtk.Box) preferences_dialog.get_content_area ();
+        vbox.set_spacing (2);
+        preferences_dialog.close.connect (preferences_dialog_close_cb);
+        preferences_dialog.response.connect (preferences_dialog_response_cb);
+
+        var notebook = new Gtk.Notebook ();
+        notebook.set_border_width (5);
+        vbox.pack_start (notebook, true, true, 0);
+
+        /* game page */
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 18);
+        vbox.set_border_width (12);
+        var label = new Gtk.Label (_("Game"));
+        notebook.append_page (vbox, label);
+
+        var frame = new GnomeGamesSupport.Frame (_("Setup"));
+        var grid = new Gtk.Grid ();
+        grid.set_row_spacing (6);
+        grid.set_column_spacing (12);
+
+        /* pre-filled rows */
+        label = new Gtk.Label.with_mnemonic (_("_Number of pre-filled rows:"));
+        label.set_alignment (0, 0.5f);
+        label.set_hexpand (true);
+        grid.attach (label, 0, 0, 1, 1);
+
+        var adj = new Gtk.Adjustment (line_fill_height, 0, LINES-1, 1, 5, 0);
+        fill_height_spinner = new Gtk.SpinButton (adj, 10, 0);
+        fill_height_spinner.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS);
+        fill_height_spinner.set_snap_to_ticks (true);
+        fill_height_spinner.value_changed.connect (fill_height_spinner_value_changed_cb);
+        grid.attach (fill_height_spinner, 1, 0, 2, 1);
+        label.set_mnemonic_widget (fill_height_spinner);
+
+        /* pre-filled rows density */
+        label = new Gtk.Label.with_mnemonic (_("_Density of blocks in a pre-filled row:"));
+        label.set_alignment (0, 0.5f);
+        label.set_hexpand (true);
+        grid.attach (label, 0, 1, 1, 1);
+
+        adj = new Gtk.Adjustment (line_fill_prob, 0, 10, 1, 5, 0);
+        fill_prob_spinner = new Gtk.SpinButton (adj, 10, 0);
+        fill_prob_spinner.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS);
+        fill_prob_spinner.set_snap_to_ticks (true);
+        fill_prob_spinner.value_changed.connect (fill_prob_spinner_value_changed_cb);
+        grid.attach (fill_prob_spinner, 1, 1, 1, 1);
+        label.set_mnemonic_widget (fill_prob_spinner);
+
+        /* starting level */
+        label = new Gtk.Label.with_mnemonic (_("_Starting level:"));
+        label.set_alignment (0, 0.5f);
+        label.set_hexpand (true);
+        grid.attach (label, 0, 2, 1, 1);
+
+        adj = new Gtk.Adjustment (starting_level, 1, 20, 1, 5, 0);
+        starting_level_spin = new Gtk.SpinButton (adj, 10.0, 0);
+        starting_level_spin.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS);
+        starting_level_spin.set_snap_to_ticks (true);
+        starting_level_spin.value_changed.connect (starting_level_value_changed_cb);
+        grid.attach (starting_level_spin, 1, 2, 1, 1);
+        label.set_mnemonic_widget (starting_level_spin);
+
+        frame.add (grid);
+        vbox.pack_start (frame, false, false, 0);
+
+        frame = new GnomeGamesSupport.Frame (_("Operation"));
+        var fvbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+
+        /* sound */
+        sound_toggle = new Gtk.CheckButton.with_mnemonic (_("_Enable sounds"));
+        sound_toggle.set_active (sound_is_enabled ());
+        sound_toggle.toggled.connect (sound_toggle_toggled_cb);
+        fvbox.pack_start (sound_toggle, false, false, 0);
+
+        /* preview next block */
+        do_preview_toggle = new Gtk.CheckButton.with_mnemonic (_("_Preview next block"));
+        do_preview_toggle.set_active (do_preview);
+        do_preview_toggle.toggled.connect (do_preview_toggle_toggled_cb);
+        fvbox.pack_start (do_preview_toggle, false, false, 0);
+
+        /* bastard mode */
+        difficult_blocks_toggle = new Gtk.CheckButton.with_mnemonic (_("Choose difficult _blocks"));
+        difficult_blocks_toggle.set_active (difficult_blocks);
+        difficult_blocks_toggle.toggled.connect (difficult_blocks_toggled_cb);
+        fvbox.pack_start (difficult_blocks_toggle, false, false, 0);
+
+        /* If bastard mode is active then disable the preview option
+            to indicate that it is unavailable in bastard mode */
+        if (difficult_blocks_toggle.get_active ())
+            do_preview_toggle.set_sensitive (false);
+
+        /* rotate counter clock wise */
+        rotate_counter_clock_wise_toggle = new Gtk.CheckButton.with_mnemonic (_("_Rotate blocks counterclockwise"));
+        rotate_counter_clock_wise_toggle.set_active (settings.get_boolean ("rotate-counter-clock-wise"));
+        rotate_counter_clock_wise_toggle.toggled.connect (set_rotate_counter_clock_wise);
+        fvbox.pack_start (rotate_counter_clock_wise_toggle, false, false, 0);
+
+        use_target_toggle = new Gtk.CheckButton.with_mnemonic (_("Show _where the block will land"));
+        fvbox.pack_start (use_target_toggle, false, false, 0);
+
+        frame.add (fvbox);
+        vbox.pack_start (frame, false, false, 0);
+
+        frame = new GnomeGamesSupport.Frame (_("Theme"));
+        grid = new Gtk.Grid ();
+        grid.set_border_width (0);
+        grid.set_row_spacing (6);
+        grid.set_column_spacing (12);
+
+        /* controls page */
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        vbox.set_border_width (12);
+        label = new Gtk.Label (_("Controls"));
+        notebook.append_page (vbox, label);
+
+        frame = new GnomeGamesSupport.Frame (_("Keyboard Controls"));
+        vbox.pack_start (frame, true, true, 0);
+
+        fvbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        frame.add (fvbox);
+
+        var controls_list = new GnomeGamesSupport.ControlsList (settings);
+        controls_list.add_controls ("key-left", _("Move left"), 0,
+                                    "key-right", _("Move right"), 0,
+                                    "key-down", _("Move down"), 0,
+                                    "key-drop", _("Drop"), 0,
+                                    "key-rotate", _("Rotate"), 0,
+                                    "key-pause", _("_pause"), 0,
+                                    null);
+
+        fvbox.pack_start (controls_list, true, true, 0);
+
+        /* theme page */
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        vbox.set_border_width (12);
+        label = new Gtk.Label (_("Theme"));
+        notebook.append_page (vbox, label);
+
+        frame = new GnomeGamesSupport.Frame (_("Block Style"));
+        vbox.pack_start (frame, true, true, 0);
+
+        fvbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        frame.add (fvbox);
+
+        var theme_combo = new Gtk.ComboBox ();
+        var theme_store = new Gtk.ListStore (2, typeof (string), typeof (string));
+        theme_combo.model = theme_store;
+        var renderer = new Gtk.CellRendererText ();
+        theme_combo.pack_start (renderer, true);
+        theme_combo.add_attribute (renderer, "text", 0);
+
+        Gtk.TreeIter iter;
+
+        theme_store.append (out iter);
+        theme_store.set (iter, 0, _("Plain"), 1, "plain", -1);
+        if (settings.get_string ("theme") == "plain")
+            theme_combo.set_active_iter (iter);
+
+        theme_store.append (out iter);
+        theme_store.set (iter, 0, _("Tango Flat"), 1, "tangoflat", -1);
+        if (settings.get_string ("theme") == "tangoflat")
+            theme_combo.set_active_iter (iter);
+
+        theme_store.append (out iter);
+        theme_store.set (iter, 0, _("Tango Shaded"), 1, "tangoshaded", -1);
+        if (settings.get_string ("theme") == "tangoshaded")
+            theme_combo.set_active_iter (iter);
+
+        theme_store.append (out iter);
+        theme_store.set (iter, 0, _("Clean"), 1, "clean", -1);
+        if (settings.get_string ("theme") == "clean")
+            theme_combo.set_active_iter (iter);
+
+        theme_combo.changed.connect (theme_combo_changed_cb);
+        fvbox.pack_start (theme_combo, false, false, 0);
+
+        theme_preview = new Preview ();
+        theme_preview.theme = settings.get_string ("theme");
+        fvbox.pack_start (theme_preview, true, true, 0);
+
+        theme_preview.set_block (4, 0, true);
+
+        preferences_dialog.show_all ();
+        new_game_action.sensitive = false;
+    }
+
+    private void sound_toggle_toggled_cb ()
+    {
+        settings.set_boolean ("sound", sound_toggle.get_active ());
+    }
+
+    private void do_preview_toggle_toggled_cb ()
+    {
+        settings.set_boolean ("do-preview", do_preview_toggle.get_active ());
+    }
+
+    private void difficult_blocks_toggled_cb ()
+    {
+        settings.set_boolean ("pick-difficult-blocks", difficult_blocks_toggle.get_active ());
+
+        /* Disable the preview option to indicate that it is unavailable in bastard mode */
+        do_preview_toggle.set_sensitive (difficult_blocks_toggle.get_active () ? false : true);
+    }
+
+    private void set_rotate_counter_clock_wise ()
+    {
+        settings.set_boolean ("rotate-counter-clock-wise", rotate_counter_clock_wise_toggle.get_active ());
+    }
+
+    private void theme_combo_changed_cb (Gtk.ComboBox widget)
+    {
+        Gtk.TreeIter iter;
+        widget.get_active_iter (out iter);
+        string theme;
+        widget.model.get (iter, 1, out theme);
+        view.theme = theme;
+        preview.theme = theme;
+        if (theme_preview != null)
+            theme_preview.theme = theme;
+        settings.set_string ("theme", theme);
+    }
+
+    private void fill_height_spinner_value_changed_cb (Gtk.SpinButton spin)
+    {
+        int value = spin.get_value_as_int ();
+        settings.set_int ("line-fill-height", value);
+    }
+
+    private void fill_prob_spinner_value_changed_cb (Gtk.SpinButton spin)
+    {
+        int value = spin.get_value_as_int ();
+        settings.set_int ("line-fill-probability", value);
+    }
+
+    private void starting_level_value_changed_cb (Gtk.SpinButton spin)
+    {
+        int value = spin.get_value_as_int ();
+        settings.set_int ("starting-level", value);
+    }
+
+    private void pause_cb ()
+    {
+        toggle_pause ();
+    }
+
+    private bool window_delete_event_cb (Gtk.Widget window, Gdk.EventAny event)
+    {
+        quit ();
+        return true;
+    }
+
+    private void quit_cb (Gtk.Action action)
+    {
+        quit ();
+    }
+
+    private void quit ()
+    {
+        /* Record the score if the game isn't over. */
+        if (in_play && score_frame.getScore () > 0)
+            high_scores.add_plain_score (score_frame.getScore ());
+
+        Gtk.main_quit ();
+    }
+
+#if 0
+    private void manage_fallen ()
+    {
+        sound_play ("land");
+
+        var level_before = score_frame.get_level ();
+
+        int level_after = score_frame.score_lines (game.check_full_lines ());
+        if (level_after != level_before)
+            sound_play ("quadrapassel");
+        if (level_before != level_after || fast_fall)
+            generate_timer (level_after);
+
+        if (game.is_field_empty ())
+            score_frame.score_last_line_bonus ();
+
+        generate ();
+    }
+#endif
+
+    private bool key_press_event_cb (Gtk.Widget widget, Gdk.EventKey event)
+    {
+        var keyval = upper_key (event.keyval);
+
+        if (keyval == upper_key (move_pause))
+        {
+            toggle_pause ();
+            return true;
+        }
+
+        if (paused)
+            return false;
+
+        if (keyval == upper_key (move_left))
+        {
+            if (game.move_left ())
+                sound_play ("slide");
+            one_pause = false;
+            return true;
+        }
+        else if (keyval == upper_key (move_right))
+        {
+            if (game.move_right ())
+                sound_play ("slide");
+            one_pause = false;
+            return true;
+        }
+        else if (keyval == upper_key (move_rotate))
+        {
+            var result = false;
+            if (settings.get_boolean ("rotate-counter-clock-wise"))
+                result = game.rotate_left ();
+            else
+                result = game.rotate_right ();
+            if (result)
+                sound_play ("turn");
+            one_pause = false;
+            return true;
+        }
+        else if (keyval == upper_key (move_down))
+        {
+            game.fast_forward (true);
+            return true;
+        }
+        else if (keyval == upper_key (move_drop))
+        {
+            game.drop ();
+            return true;
+        }
+
+        return false;
+    }
+
+    private bool key_release_event_cb (Gtk.Widget widget, Gdk.EventKey event)
+    {
+        var keyval = upper_key (event.keyval);
+
+        if (keyval == upper_key (move_down))
+        {
+            game.fast_forward (false);
+            return true;
+        }
+
+        return false;
+    }
+
+    private uint upper_key (uint keyval)
+    {
+        if (keyval > 255)
+            return keyval;
+        return ((char) keyval).toupper ();
+    }
+
+    private void toggle_pause ()
+    {
+        paused = !paused;
+
+        if (paused)
+            view.show_pause_message ();
+        else
+            view.hide_pause_message ();
+    }
+
+#if 0
+    private void end_of_game ()
+    {
+        if (paused)
+            toggle_pause ();
+        pause_action.sensitive = false;
+        preferences_action.sensitive = true;
+
+        block_color_next = -1;
+        block_index_next = -1;
+        block_rotation_next = -1;
+        preview.set_block (-1, -1, false);
+        view.hide_pause_message ();
+        view.show_game_over_message ();
+        sound_play ("gameover");
+        in_play = false;
+
+        if (score_frame.getScore () > 0)
+        {
+            var pos = high_scores.add_plain_score (score_frame.getScore ());
+            var dialog = new GnomeGamesSupport.ScoresDialog (w, high_scores, _("Quadrapassel Scores"));
+            dialog.set_hilight (pos);
+            dialog.run ();
+            dialog.destroy ();
+        }
+    }
+#endif
+
+    private void new_game_cb (Gtk.Action action)
+    {
+        in_play = true;
+
+        int level = cmdline_level != 0 ? cmdline_level : starting_level;
+
+        fast_fall = false;
+
+        score_frame.set_level (level);
+        score_frame.set_starting_level (level);
+
+        score_frame.reset_score ();
+        paused = false;
+
+        game.start (line_fill_height, line_fill_prob);
+        if (do_preview)
+            preview.set_block (block_color_next, block_color_next, false);
+
+        pause_action.sensitive = true;
+        preferences_action.sensitive = false;
+
+        view.hide_pause_message ();
+        view.hide_game_over_message ();
+
+        sound_play ("quadrapassel");
+    }
+
+    private void help_cb (Gtk.Action action)
+    {
+        try
+        {
+            Gtk.show_uri (w.get_screen (), "ghelp:quadrapassel", Gtk.get_current_event_time ());
+        }
+        catch (Error e)
+        {
+            warning ("Failed to show help: %s", e.message);
+        }
+    }
+
+    private void about_cb (Gtk.Action action)
+    {
+        string[] authors = { "Gnome Games Team", null };
+        string[] documenters = { "Angela Boyle", null };
+
+        Gtk.show_about_dialog (w,
+                               "program-name", _("Quadrapassel"),
+                               "version", VERSION,
+                               "comments", _("A classic game of fitting falling blocks together.\n\nQuadrapassel is a part of GNOME Games."),
+                               "copyright", "Copyright \xc2\xa9 1999 J. Marcin Gorycki, 2000-2009 Others",
+                               "license", GnomeGamesSupport.get_license (_("Quadrapassel")),
+                               "website-label", _("GNOME Games web site"),
+                               "authors", authors,
+                               "documenters", documenters,
+                               "translator-credits", _("translator-credits"),
+                               "logo-icon-name", "quadrapassel",
+                               "website", "http://www.gnome.org/projects/gnome-games/";,
+                               "wrap-license", true,
+                               null);
+    }
+
+    private void scores_cb (Gtk.Action action)
+    {
+        var dialog = new GnomeGamesSupport.ScoresDialog (w, high_scores, _("Quadrapassel Scores"));
+        dialog.set_hilight (0);
+        dialog.run ();
+        dialog.destroy ();
+    }
+
+    public static int main (string args[])
+    {
+        var context = new OptionContext ("");
+
+        context.add_group (Gtk.get_option_group (true));
+        context.add_group (Clutter.get_option_group_without_init ());
+
+        try
+        {
+            context.parse (ref args);
+        }
+        catch (Error e)
+        {
+            stderr.printf ("%s\n", e.message);
+            return Posix.EXIT_FAILURE;
+        }
+
+        Environment.set_application_name (_("Quadrapassel"));
+
+        Gtk.Window.set_default_icon_name ("quadrapassel");
+
+        try
+        {
+            GtkClutter.init_with_args (ref args, "", new OptionEntry[0], null);
+        }
+        catch (Error e)
+        {
+            var dialog = new Gtk.MessageDialog (null, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.NONE, "Unable to initialize Clutter:\n%s", e.message);
+            dialog.set_title (Environment.get_application_name ());
+            dialog.run ();
+            dialog.destroy ();
+            return Posix.EXIT_FAILURE;
+        }
+
+        var app = new Quadrapassel (cmdline_level);
+        app.show ();
+
+        Gtk.main ();
+
+        return Posix.EXIT_SUCCESS;
+    }
+}
diff --git a/quadrapassel/src/scoreframe.vala b/quadrapassel/src/scoreframe.vala
new file mode 100644
index 0000000..39e4f02
--- /dev/null
+++ b/quadrapassel/src/scoreframe.vala
@@ -0,0 +1,139 @@
+public class ScoreFrame
+{
+    private Gtk.Grid w;
+    private Gtk.Label scorew;
+    private Gtk.Label linesw;
+    private Gtk.Label levelw;
+    private int level;
+    private int score;
+    private int lines;
+    private int starting_level;
+
+    public ScoreFrame (int cmdl_level)
+    {
+        starting_level = cmdl_level;
+        starting_level = starting_level.clamp (1, 20);
+
+        level = starting_level;
+
+        w = new Gtk.Grid ();
+
+        var score_label = new Gtk.Label (_("Score:"));
+        score_label.set_alignment (0.0f, 0.5f);
+        w.attach (score_label, 0, 0, 1, 1);
+        scorew = new Gtk.Label ("0");
+        scorew.set_alignment (1.0f, 0.5f);
+        w.attach (scorew, 1, 0, 1, 1);
+
+        var linesLabel = new Gtk.Label (_("Lines:"));
+        linesLabel.set_alignment (0.0f, 0.5f);
+        w.attach (linesLabel, 0, 1, 1, 1);
+        linesw = new Gtk.Label ("0");
+        linesw.set_alignment (1.0f, 0.5f);
+        w.attach (linesw, 1, 1, 1, 1);
+
+        var levelLabel = new Gtk.Label (_("Level:"));
+        levelLabel.set_alignment (0.0f, 0.5f);
+        w.attach (levelLabel, 0, 2, 1, 1);
+        levelw = new Gtk.Label ("0");
+        levelw.set_alignment (1.0f, 0.5f);
+        w.attach (levelw, 1, 2, 1, 1);
+    }
+
+    public void show ()
+    {
+        w.show_all ();
+    }
+    
+    public Gtk.Widget get_widget ()
+    {
+        return w;
+    }
+    
+    public int getScore ()
+    {
+        return score;
+    }
+
+    public void set_score (int s)
+    {
+        score = s;
+        scorew.set_text ("%7d".printf (score));
+    }
+
+    public void inc_score (int s)
+    {
+        set_score (score + s);
+    }
+
+    // The bonus for clearing everything.
+    public void score_last_line_bonus ()
+    {
+        inc_score (10000*level);
+        // FIXME: Get it its own sound?
+        sound_play ("quadrapassel");
+    }
+
+    public int score_lines (int newlines)
+    {
+        int linescore = 0;
+
+        switch (newlines)
+        {
+            case 0:
+                return level;
+            case 1:
+                linescore = 40;
+                sound_play ("lines1");
+                break;
+            case 2:
+                linescore = 100;
+                sound_play ("lines2");
+                break;
+            case 3:
+                linescore = 300;
+                sound_play ("lines3");
+                break;
+            case 4:
+                linescore = 1200;
+                sound_play ("lines3");
+                break;
+        }
+        inc_score (level * linescore);
+
+        // check the level
+        set_lines (lines + newlines);
+        int l = starting_level + lines / 10;
+        set_level (l);
+
+        return level;
+    }
+    
+    public int get_level ()
+    {
+        return level;
+    }
+
+    public void set_level (int l)
+    {
+        level = l;
+        levelw.set_text ("%7d".printf (level));
+    }
+
+    public void set_lines (int l)
+    {
+        lines = l;
+        linesw.set_text ("%7d".printf (lines));
+    }
+
+    public void reset_score ()
+    {
+        set_lines (0);
+        set_score (0);
+    }
+
+    public void set_starting_level (int l)
+    {
+        starting_level = l;
+    }
+}
diff --git a/quadrapassel/src/sound.vala b/quadrapassel/src/sound.vala
new file mode 100644
index 0000000..5017732
--- /dev/null
+++ b/quadrapassel/src/sound.vala
@@ -0,0 +1,21 @@
+static bool enabled = true;
+
+void sound_enable (bool enable)
+{
+    enabled = enable;
+}
+
+bool sound_is_enabled ()
+{
+    return enabled;
+}
+
+void sound_play (string name)
+{
+    if (!enabled)
+        return;
+
+    CanberraGtk.context_get_for_screen (null).play (0,
+                                                    Canberra.PROP_MEDIA_NAME, name,
+                                                    Canberra.PROP_MEDIA_FILENAME, Path.build_filename (SOUND_DIRECTORY, "%s.ogg".printf (name)));
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]