[gnome-games] mahjongg: Port from C to Vala



commit 15ed6a32a5c99b238deb076eef8be75093138a79
Author: Robert Ancell <robert ancell canonical com>
Date:   Sat Oct 22 16:28:20 2011 +1100

    mahjongg: Port from C to Vala

 configure.in                                       |    4 +-
 libgames-support/GnomeGamesSupport-1.0.vapi        |  235 ++++
 libgames-support/Makefile.am                       |    4 -
 libgames-support/games-files.c                     |   16 +-
 libgames-support/games-files.h                     |    3 +-
 libgames-support/games-scores.c                    |   11 +-
 libgames-support/games-scores.h                    |    2 +-
 mahjongg/Makefile.am                               |   96 +--
 mahjongg/data/Makefile.am                          |   46 +
 mahjongg/{ => data}/mahjongg.6                     |    0
 mahjongg/{ => data}/mahjongg.desktop.in.in         |    0
 mahjongg/{ => data}/mahjongg.map                   |    0
 .../{ => data}/org.gnome.mahjongg.gschema.xml.in   |    0
 mahjongg/{ => data}/postmodern.svg                 |    0
 mahjongg/{ => data}/smooth.png                     |  Bin 336403 -> 336403 bytes
 mahjongg/{ => data}/translatable_game_names.h      |    0
 mahjongg/drawing.c                                 |  460 ------
 mahjongg/drawing.h                                 |   27 -
 mahjongg/get_titles.pl                             |   41 -
 mahjongg/mahjongg.c                                | 1457 --------------------
 mahjongg/mahjongg.h                                |   44 -
 mahjongg/maps.c                                    |  578 --------
 mahjongg/maps.h                                    |   38 -
 mahjongg/solubility.c                              |  530 -------
 mahjongg/solubility.h                              |   21 -
 mahjongg/src/Makefile.am                           |   54 +
 mahjongg/src/config.vapi                           |    2 +
 mahjongg/src/game-view.vala                        |  258 ++++
 mahjongg/src/game.vala                             |  383 +++++
 mahjongg/src/mahjongg.vala                         |  799 +++++++++++
 mahjongg/src/map.vala                              |  358 +++++
 po/POTFILES.in                                     |   13 +-
 po/POTFILES.skip                                   |    6 +-
 33 files changed, 2173 insertions(+), 3313 deletions(-)
---
diff --git a/configure.in b/configure.in
index 636879d..6052009 100644
--- a/configure.in
+++ b/configure.in
@@ -853,7 +853,9 @@ gnomine/help/Makefile
 swell-foop/Makefile
 swell-foop/help/Makefile
 mahjongg/Makefile
+mahjongg/data/Makefile
 mahjongg/help/Makefile
+mahjongg/src/Makefile
 gtali/Makefile
 gtali/pix/Makefile
 gtali/help/Makefile
@@ -905,7 +907,7 @@ gnobots2/gnobots2.desktop.in
 gnibbles/gnibbles.desktop.in
 gnotski/gnotski.desktop.in
 glchess/glchess.desktop.in
-mahjongg/mahjongg.desktop.in
+mahjongg/data/mahjongg.desktop.in
 gtali/gtali.desktop.in
 gnome-sudoku/gnome-sudoku.desktop.in
 iagno/iagno.desktop.in
diff --git a/libgames-support/GnomeGamesSupport-1.0.vapi b/libgames-support/GnomeGamesSupport-1.0.vapi
new file mode 100644
index 0000000..56e3f2e
--- /dev/null
+++ b/libgames-support/GnomeGamesSupport-1.0.vapi
@@ -0,0 +1,235 @@
+/* We should probably be using the GIR files, but I can't get them to work in
+ * Vala.  This works for now but means it needs to be updated when the library
+ * changes -- Robert Ancell */
+
+[CCode (cprefix = "Games", lower_case_cprefix = "games_")]
+namespace GnomeGamesSupport
+{
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_SCORES;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_PAUSE_GAME;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_RESUME_GAME;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_FULLSCREEN;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_LEAVE_FULLSCREEN;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_NEW_GAME;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_START_NEW_GAME;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_NETWORK_GAME;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_NETWORK_LEAVE;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_PLAYER_LIST;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_RESTART_GAME;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_UNDO_MOVE;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_REDO_MOVE;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_HINT;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_END_GAME;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_CONTENTS;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_RESET;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_TELEPORT;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_RTELEPORT;
+    [CCode (cheader_filename = "games-stock.h")]
+    public const string STOCK_DEAL_CARDS;
+
+    [CCode (cheader_filename = "games-stock.h")]
+    public static void stock_init ();
+    [CCode (cheader_filename = "games-stock.h")]
+    public static void stock_prepare_for_statusbar_tooltips (Gtk.UIManager ui_manager, Gtk.Widget statusbar);
+    [CCode (cheader_filename = "games-stock.h")]
+    public static string get_license (string game_name);
+
+    [CCode (cheader_filename = "games-setgid-io.h", cname = "setgid_io_init")]
+    public static void setgid_io_init ();
+
+    [CCode (cprefix = "GAMES_RUNTIME_", cheader_filename = "games-runtime.h")]
+    public enum RuntimeDirectory
+    {
+        PREFIX,
+        DATA_DIRECTORY,
+        COMMON_DATA_DIRECTORY,
+        PKG_DATA_DIRECTORY,
+        SCORES_DIRECTORY,
+        LOCALE_DIRECTORY,
+        COMMON_PIXMAP_DIRECTORY,
+        PRERENDERED_CARDS_DIRECTORY,
+        SCALABLE_CARDS_DIRECTORY,
+        ICON_THEME_DIRECTORY,
+        PIXMAP_DIRECTORY,
+        SOUND_DIRECTORY,
+        GAME_DATA_DIRECTORY,
+        GAME_GAMES_DIRECTORY,
+        GAME_PIXMAP_DIRECTORY,
+        GAME_THEME_DIRECTORY,
+        GAME_HELP_DIRECTORY,
+        LAST_DIRECTORY,
+        FIRST_DERIVED_DIRECTORY
+    }
+
+    [CCode (cheader_filename = "games-runtime.h")]
+    public static bool runtime_init (string name);
+    [CCode (cheader_filename = "games-runtime.h")]
+    public static void runtime_shutdown ();
+    [CCode (cheader_filename = "games-runtime.h")]
+    public static unowned string runtime_get_directory (RuntimeDirectory directory);
+    [CCode (cheader_filename = "games-runtime.h")]
+    public static unowned string runtime_get_file (RuntimeDirectory directory, string name);
+    [CCode (cheader_filename = "games-runtime.h")]
+    public static int runtime_get_gpl_version  ();
+    [CCode (cheader_filename = "games-runtime.h")]
+    public static bool runtime_is_system_prefix ();
+    
+    [CCode (cheader_filename = "games-help.h")]
+    public static void help_display (Gtk.Widget window, string doc_module, string? section);
+    [CCode (cheader_filename = "games-help.h")]
+    public static bool help_display_full (Gtk.Widget window, string doc_module, string? section) throws GLib.Error;
+    
+    [CCode (cheader_filename = "games-settings.h")]    
+    public static void settings_bind_window_state (string path, Gtk.Window window);
+
+    [CCode (cheader_filename = "games-clock.h")]
+    public class Clock : Gtk.Label
+    {
+        public Clock ();
+        public void start ();
+        public bool is_started ();
+        public void stop ();
+        public void reset ();
+        public time_t get_seconds ();
+        public void add_seconds (time_t seconds);
+        public void set_updated (bool do_update);
+    }
+    
+    [CCode (cheader_filename = "games-pause-action.h")]
+    public class PauseAction : Gtk.Action
+    {
+        public signal void state_changed ();
+        public PauseAction (string name);
+        public bool get_is_paused ();
+    }
+
+    [CCode (cprefix = "GAMES_FULLSCREEN_ACTION_VISIBLE_")]
+    public enum VisiblePolicy
+    {
+        ALWAYS,
+        ON_FULLSCREEN,
+        ON_UNFULLSCREEN
+    }
+
+    [CCode (cheader_filename = "games-fullscreen-action.h")]
+    public class FullscreenAction : Gtk.Action
+    {
+        public FullscreenAction (string name, Gtk.Window window);
+        public void set_visible_policy (VisiblePolicy visible_policy);
+        public VisiblePolicy get_visible_policy ();
+        public void set_is_fullscreen (bool is_fullscreen);
+        public bool get_is_fullscreen ();
+    }
+
+    [CCode (cprefix = "GAMES_SCORES_STYLE_", cheader_filename = "games-score.h")]
+    public enum ScoreStyle
+    {
+        PLAIN_DESCENDING,
+        PLAIN_ASCENDING,
+        TIME_DESCENDING,
+        TIME_ASCENDING
+    }
+    
+    [CCode (cheader_filename = "games-scores.h", destroy_function = "")]
+    public struct ScoresCategory
+    {
+        string key;
+        string name;
+    }
+
+    [CCode (cheader_filename = "games-score.h")]
+    public class Score : GLib.Object
+    {
+        public Score ();
+    }
+
+    [CCode (cheader_filename = "games-scores.h")]
+    public class Scores : GLib.Object
+    {
+        public Scores (string app_name, ScoresCategory[] categories, string? categories_context, string? categories_domain, int default_category_index, ScoreStyle style);
+        public void set_category (string category);
+        public int add_score (Score score);
+        public int add_plain_score (uint32 value);
+        public int add_time_score (double value);
+        public void update_score (string new_name);
+        public void update_score_name (string new_name, string old_name);
+        public ScoreStyle get_style ();
+        public unowned string get_category ();
+        public void add_category (string key, string name);
+    }
+    
+    [CCode (cprefix = "GAMES_SCORES_", cheader_filename = "games-scores-dialog.h")]
+    public enum ScoresButtons
+    {
+        CLOSE_BUTTON,
+        NEW_GAME_BUTTON,
+        UNDO_BUTTON,
+        QUIT_BUTTON
+    }
+
+    [CCode (cheader_filename = "games-scores-dialog.h")]
+    public class ScoresDialog : Gtk.Dialog
+    {
+        public ScoresDialog (Gtk.Window parent_window, Scores scores, string title);
+        public void set_category_description (string description);
+        public void set_hilight (uint pos);
+        public void set_message (string message);
+        public void set_buttons (uint buttons);
+    }
+
+    [CCode (cheader_filename = "games-frame.h")]
+    public class Frame : Gtk.Box
+    {
+        public Frame (string label);
+        void set_label (string label);
+    }
+
+    [CCode (cheader_filename = "games-preimage.h")]
+    public class Preimage : GLib.Object
+    {
+        public Preimage ();
+        public Preimage.from_file (string filename) throws GLib.Error;
+        public void set_font_options (Cairo.FontOptions font_options);
+        public Gdk.Pixbuf render (int width, int height);
+        public void render_cairo (Cairo.Context cr, int width, int height);
+        public Gdk.Pixbuf render_sub (string node, int width, int height, double xoffset, double yoffset, double xzoom, double yzoom);
+        public void render_cairo_sub (Cairo.Context cr, string node, int width, int height, double xoffset, double yoffset, double xzoom, double yzoom);
+        public bool is_scalable ();
+        public int get_width ();
+        public int get_height ();
+        public Gdk.Pixbuf render_unscaled_pixbuf ();
+    }
+
+    [CCode (cheader_filename = "games-files.h")]
+    public class FileList : GLib.Object
+    {
+        public FileList (string glob, ...);
+        public FileList.images (string path1, ...);
+        public void transform_basename ();
+        public size_t length ();
+        public void for_each (GLib.Func<string> function);
+        public string find (GLib.CompareFunc function);
+        public unowned string? get_nth (int n);
+        public Gtk.Widget create_widget (string selection, uint flags);
+    }
+}
+
diff --git a/libgames-support/Makefile.am b/libgames-support/Makefile.am
index c963b80..095921c 100644
--- a/libgames-support/Makefile.am
+++ b/libgames-support/Makefile.am
@@ -192,16 +192,12 @@ GnomeGamesSupport-1.0.gir: $(INTROSPECTION_SCANNER) libgames-support-gi.la $(lib
 	--namespace GnomeGamesSupport --nsversion=1.0 \
 	--identifier-prefix=Games --symbol-prefix=games_ --accept-unprefixed \
 	--add-include-path=$(srcdir) --add-include=path=. \
-	--include=Clutter-1.0 \
-	--include=Cogl-1.0 \
 	--include=Gtk-$(GTK_API_VERSION) \
 	--library=games-support-gi \
 	--libtool="$(LIBTOOL)" \
 	--output $@ \
 	--pkg gobject-2.0 \
 	--pkg gtk+-$(GTK_API_VERSION) \
-	--pkg clutter-1.0 \
-	--pkg cogl-1.0 \
 	--warn-all \
 	-I$(top_srcdir) \
 	-I$(top_builddir) \
diff --git a/libgames-support/games-files.c b/libgames-support/games-files.c
index eb2a646..6b577e6 100644
--- a/libgames-support/games-files.c
+++ b/libgames-support/games-files.c
@@ -350,6 +350,18 @@ games_file_list_create_widget (GamesFileList * filelist,
 }
 
 /**
+ * games_file_list_length:
+ * @filelist: The list of files to use.
+ * 
+ * Get the number of elements in the file list.
+ **/
+gsize
+games_file_list_length (GamesFileList * filelist)
+{
+    return g_list_length (filelist->priv->list);
+}
+
+/**
  * games_file_list_for_each:
  * @filelist: The file list to iterate over.
  * @function: (scope call): The function to call on each item. It gets called with two 
@@ -403,10 +415,10 @@ games_file_list_find (GamesFileList * filelist, GCompareFunc function,
  * Return value: 
  **/
 /* Return the nth filename in the list. */
-gchar *
+const gchar *
 games_file_list_get_nth (GamesFileList * filelist, gint n)
 {
-  return (gchar *) g_list_nth_data (filelist->priv->list, n);
+  return (const gchar *) g_list_nth_data (filelist->priv->list, n);
 }
 
 static void
diff --git a/libgames-support/games-files.h b/libgames-support/games-files.h
index 30e1294..0e1fcc8 100644
--- a/libgames-support/games-files.h
+++ b/libgames-support/games-files.h
@@ -50,13 +50,14 @@ GamesFileList *games_file_list_new                (const gchar * glob,
 GamesFileList *games_file_list_new_images         (const gchar * path1,
                                                    ...) G_GNUC_NULL_TERMINATED;
 void           games_file_list_transform_basename (GamesFileList * list);
+gsize          games_file_list_length             (GamesFileList * filelist);
 void           games_file_list_for_each           (GamesFileList * filelist,
                                                    GFunc function,
                                                    gpointer userdata);
 gchar         *games_file_list_find               (GamesFileList * filelist,
                                                    GCompareFunc function,
                                                    gpointer userdata);
-gchar         *games_file_list_get_nth            (GamesFileList * filelist,
+const gchar   *games_file_list_get_nth            (GamesFileList * filelist,
                                                    gint n);
 GtkWidget     *games_file_list_create_widget      (GamesFileList * filelist,
                                                    const gchar * selection,
diff --git a/libgames-support/games-scores.c b/libgames-support/games-scores.c
index 3532f75..d385297 100644
--- a/libgames-support/games-scores.c
+++ b/libgames-support/games-scores.c
@@ -128,10 +128,6 @@ games_scores_new (const char *app_name,
 
   /* FIXME: Input sanity checks. */
 
-  self->priv->categories = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                  g_free,
-                                                  (GDestroyNotify) games_scores_category_free);
-
   /* catsordered is a record of the ordering of the categories. 
    * Its data is shared with the hash table. */
   self->priv->catsordered = NULL;
@@ -210,7 +206,7 @@ games_scores_add_category (GamesScores *self,
  *
  **/
 void
-games_scores_set_category (GamesScores * self, gchar * category)
+games_scores_set_category (GamesScores * self, const gchar * category)
 {
   GamesScoresPrivate *priv = self->priv;
 
@@ -494,6 +490,9 @@ games_scores_init (GamesScores * self)
   priv->last_score_significant = FALSE;
   priv->last_score_position = 0;
   priv->last_score = games_score_new ();
+  priv->categories = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                            g_free,
+                                            (GDestroyNotify) games_scores_category_free);
 }
 
 static void
@@ -502,7 +501,7 @@ games_scores_finalize (GObject * object)
   GamesScores *scores = GAMES_SCORES (object);
 
   g_hash_table_unref (scores->priv->categories);
-  g_free (scores->priv->catsordered);
+  g_slist_free (scores->priv->catsordered);
   g_free (scores->priv->currentcat);
   g_free (scores->priv->defcat);
   g_free (scores->priv->basename);
diff --git a/libgames-support/games-scores.h b/libgames-support/games-scores.h
index 8b2ea74..388d68d 100644
--- a/libgames-support/games-scores.h
+++ b/libgames-support/games-scores.h
@@ -70,7 +70,7 @@ GamesScores    *games_scores_new               (const char *app_name,
                                                 const char *categories_domain,
                                                 int default_category_index,
                                                 GamesScoreStyle style);
-void            games_scores_set_category      (GamesScores * self, gchar * category);
+void            games_scores_set_category      (GamesScores * self, const gchar * category);
 gint            games_scores_add_score         (GamesScores * self, GamesScore *score);
 gint            games_scores_add_plain_score   (GamesScores * self, guint32 value);
 gint            games_scores_add_time_score    (GamesScores * self, gdouble value);
diff --git a/mahjongg/Makefile.am b/mahjongg/Makefile.am
index b99619d..b814dd2 100644
--- a/mahjongg/Makefile.am
+++ b/mahjongg/Makefile.am
@@ -1,101 +1,7 @@
-SUBDIRS =
+SUBDIRS = data src
 
 if BUILD_HELP
 SUBDIRS += help
 endif
 
-NULL =
-
-mapdir = $(pkgdatadir)/mahjongg/games
-map_DATA = \
-	mahjongg.map	\
-	$(NULL)
-
-pixmapdir = $(pkgdatadir)/mahjongg/pixmaps
-pixmap_DATA = \
-	smooth.png	\
-	postmodern.svg	\
-	$(NULL)
-
-bin_PROGRAMS = mahjongg
-
-mahjongg_SOURCES = 	\
-	drawing.c	\
-	drawing.h	\
-	mahjongg.c	\
-	mahjongg.h	\
-	maps.c		\
-	maps.h		\
-	solubility.c	\
-	solubility.h	\
-	translatable_game_names.h	\
-	$(NULL)
-
-mahjongg_CPPFLAGS = \
-	-I$(top_srcdir)					\
-	$(AM_CPPFLAGS)
-
-mahjongg_CFLAGS = \
-	$(GTK_CFLAGS)	\
-	$(AM_CFLAGS)
-		
-mahjongg_LDADD = \
-	$(top_builddir)/libgames-support/libgames-support.la \
-	$(GTK_LIBS)	\
-	$(INTLLIBS)	\
-	$(NULL)
-
-if HAVE_GNOME
-mahjongg_CFLAGS += $(GNOME_CFLAGS)
-mahjongg_LDADD += $(GNOME_LIBS)
-endif
-
-if HAVE_RSVG
-mahjongg_CFLAGS += $(RSVG_CFLAGS)
-mahjongg_LDADD += $(RSVG_LIBS)
-endif
-
-if WITH_GTHREAD
-mahjongg_CFLAGS += $(GHTREAD_CFLAGS)
-mahjongg_LDADD += $(GTHREAD_LIBS)
-endif
-
-gsettings_in_file = org.gnome.mahjongg.gschema.xml.in
-gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml)
- INTLTOOL_XML_NOMERGE_RULE@
- GSETTINGS_RULES@
-
-man_MANS = mahjongg.6
-
-desktop_in_files = mahjongg.desktop.in.in
-desktopdir = $(datadir)/applications
-desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
-
-EXTRA_DIST = \
-	$(pixmap_DATA)		\
-	$(gsettings_in_file)	\
-	$(man_MANS)	\
-	$(map_DATA)		\
-	$(NULL)
-
-CLEANFILES = $(desktop_DATA) $(gsettings_SCHEMAS)
-DISTCLEANFILES = $(desktop_DATA) $(gsettings_SCHEMAS)
-
-install-scorefiles-local:
-	-$(mkinstalldirs) $(DESTDIR)$(scoredir)
-	-for i in easy difficult confounding pyramid tictactoe cloud dragon bridges ziggurat; do \
-		touch $(DESTDIR)$(scoredir)/mahjongg.$$i.scores; \
-		chown $(scores_user):$(scores_group) $(DESTDIR)$(scoredir)/mahjongg.$$i.scores; \
-		chmod 664 $(DESTDIR)$(scoredir)/mahjongg.$$i.scores; \
-	done
-
-install-exec-hook:
-	-if test "$(setgid)" = "true"; then \
-	  chgrp $(scores_group) $(DESTDIR)$(bindir)/mahjongg && chmod 2555 $(DESTDIR)$(bindir)/mahjongg ;\
-	fi
-
-install-data-local: install-scorefiles-local
-
- INTLTOOL_DESKTOP_RULE@
-
 -include $(top_srcdir)/git.mk
diff --git a/mahjongg/data/Makefile.am b/mahjongg/data/Makefile.am
new file mode 100644
index 0000000..8c47317
--- /dev/null
+++ b/mahjongg/data/Makefile.am
@@ -0,0 +1,46 @@
+mapdir = $(pkgdatadir)/mahjongg/games
+map_DATA = \
+	mahjongg.map	\
+	$(NULL)
+
+pixmapdir = $(pkgdatadir)/mahjongg/pixmaps
+pixmap_DATA = \
+	smooth.png	\
+	postmodern.svg	\
+	$(NULL)
+
+gsettings_in_file = org.gnome.mahjongg.gschema.xml.in
+gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml)
+ INTLTOOL_XML_NOMERGE_RULE@
+ GSETTINGS_RULES@
+
+man_MANS = mahjongg.6
+
+desktop_in_files = mahjongg.desktop.in.in
+desktopdir = $(datadir)/applications
+desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
+
+EXTRA_DIST = \
+	$(pixmap_DATA)		\
+	$(gsettings_in_file)	\
+	$(man_MANS)	\
+	$(map_DATA)		\
+	translatable_game_names.h	\
+	$(NULL)
+
+CLEANFILES = $(desktop_DATA) $(gsettings_SCHEMAS)
+DISTCLEANFILES = $(desktop_DATA) $(gsettings_SCHEMAS)
+
+install-scorefiles-local:
+	-$(mkinstalldirs) $(DESTDIR)$(scoredir)
+	-for i in easy difficult confounding pyramid tictactoe cloud dragon bridges ziggurat; do \
+		touch $(DESTDIR)$(scoredir)/mahjongg.$$i.scores; \
+		chown $(scores_user):$(scores_group) $(DESTDIR)$(scoredir)/mahjongg.$$i.scores; \
+		chmod 664 $(DESTDIR)$(scoredir)/mahjongg.$$i.scores; \
+	done
+
+install-data-local: install-scorefiles-local
+
+ INTLTOOL_DESKTOP_RULE@
+
+-include $(top_srcdir)/git.mk
diff --git a/mahjongg/mahjongg.6 b/mahjongg/data/mahjongg.6
similarity index 100%
rename from mahjongg/mahjongg.6
rename to mahjongg/data/mahjongg.6
diff --git a/mahjongg/mahjongg.desktop.in.in b/mahjongg/data/mahjongg.desktop.in.in
similarity index 100%
rename from mahjongg/mahjongg.desktop.in.in
rename to mahjongg/data/mahjongg.desktop.in.in
diff --git a/mahjongg/mahjongg.map b/mahjongg/data/mahjongg.map
similarity index 100%
rename from mahjongg/mahjongg.map
rename to mahjongg/data/mahjongg.map
diff --git a/mahjongg/org.gnome.mahjongg.gschema.xml.in b/mahjongg/data/org.gnome.mahjongg.gschema.xml.in
similarity index 100%
rename from mahjongg/org.gnome.mahjongg.gschema.xml.in
rename to mahjongg/data/org.gnome.mahjongg.gschema.xml.in
diff --git a/mahjongg/postmodern.svg b/mahjongg/data/postmodern.svg
similarity index 100%
rename from mahjongg/postmodern.svg
rename to mahjongg/data/postmodern.svg
diff --git a/mahjongg/smooth.png b/mahjongg/data/smooth.png
similarity index 100%
rename from mahjongg/smooth.png
rename to mahjongg/data/smooth.png
diff --git a/mahjongg/translatable_game_names.h b/mahjongg/data/translatable_game_names.h
similarity index 100%
rename from mahjongg/translatable_game_names.h
rename to mahjongg/data/translatable_game_names.h
diff --git a/mahjongg/src/Makefile.am b/mahjongg/src/Makefile.am
new file mode 100644
index 0000000..87ee864
--- /dev/null
+++ b/mahjongg/src/Makefile.am
@@ -0,0 +1,54 @@
+bin_PROGRAMS = mahjongg
+
+mahjongg_SOURCES = 	\
+	config.vapi	\
+	game.vala	\
+	game-view.vala	\
+	mahjongg.vala	\
+	map.vala	\
+	$(NULL)
+
+mahjongg_VALAFLAGS = \
+	--pkg posix \
+	--pkg gtk+-3.0 \
+	--vapidir $(top_srcdir)/libgames-support \
+	--pkg GnomeGamesSupport-1.0
+
+mahjongg_CFLAGS = \
+	-I$(top_srcdir)/libgames-support \
+	-DVERSION=\"$(VERSION)\" \
+	-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
+	$(GTK_CFLAGS)	\
+	$(AM_CFLAGS)
+
+if ENABLE_SETGID
+mahjongg_VALAFLAGS += -D ENABLE_SETGID
+endif
+		
+mahjongg_LDADD = \
+	$(top_builddir)/libgames-support/libgames-support.la \
+	$(GTK_LIBS)	\
+	$(INTLLIBS)	\
+	$(NULL)
+
+if HAVE_GNOME
+mahjongg_CFLAGS += $(GNOME_CFLAGS)
+mahjongg_LDADD += $(GNOME_LIBS)
+endif
+
+if HAVE_RSVG
+mahjongg_CFLAGS += $(RSVG_CFLAGS)
+mahjongg_LDADD += $(RSVG_LIBS)
+endif
+
+if WITH_GTHREAD
+mahjongg_CFLAGS += $(GHTREAD_CFLAGS)
+mahjongg_LDADD += $(GTHREAD_LIBS)
+endif
+
+install-exec-hook:
+	-if test "$(setgid)" = "true"; then \
+	  chgrp $(scores_group) $(DESTDIR)$(bindir)/mahjongg && chmod 2555 $(DESTDIR)$(bindir)/mahjongg ;\
+	fi
+
+-include $(top_srcdir)/git.mk
diff --git a/mahjongg/src/config.vapi b/mahjongg/src/config.vapi
new file mode 100644
index 0000000..6477226
--- /dev/null
+++ b/mahjongg/src/config.vapi
@@ -0,0 +1,2 @@
+public const string VERSION;
+public const string GETTEXT_PACKAGE;
\ No newline at end of file
diff --git a/mahjongg/src/game-view.vala b/mahjongg/src/game-view.vala
new file mode 100644
index 0000000..cea2be2
--- /dev/null
+++ b/mahjongg/src/game-view.vala
@@ -0,0 +1,258 @@
+public class GameView : Gtk.DrawingArea
+{
+    public Gdk.Color background_color;
+    private Gdk.Pixbuf? tile_textures = null;
+    private int tile_texture_width = 0;
+    private int tile_texture_height = 0;
+    
+    private int x_offset;
+    private int y_offset;
+    private int tile_width;
+    private int tile_height;
+    private int tile_layer_offset_x;
+    private int tile_layer_offset_y;
+
+    private Game? _game;
+    public Game? game
+    {
+        get { return _game; }
+        set
+        {
+            _game = value;
+            _game.redraw_tile.connect (redraw_tile_cb);
+            queue_draw ();
+        }
+    }
+
+    private GnomeGamesSupport.Preimage? _theme = null;
+    public GnomeGamesSupport.Preimage? theme
+    {
+        get { return _theme; }
+        set { _theme = value; tile_textures = null; queue_draw (); }
+    }
+    
+    private bool _paused = false;
+    public bool paused
+    {
+        get { return _paused; }
+        set { _paused = value; queue_draw (); }
+    }
+
+    public GameView ()
+    {
+        can_focus = true;
+        add_events (Gdk.EventMask.BUTTON_PRESS_MASK);
+    }
+
+    public void set_background (string? colour)
+    {
+        if (colour == null || !Gdk.Color.parse (colour, out background_color))
+            background_color.red = background_color.green = background_color.blue = 0;
+        queue_draw ();
+    }
+
+    public override bool configure_event (Gdk.EventConfigure event)
+    {
+        /* Regenerate images */
+        tile_textures = null;
+
+        return false;
+    }
+    
+    private void draw_game (Cairo.Context cr, bool render_indexes = false)
+    {
+        if (theme == null)
+            return;
+
+        update_dimensions ();
+
+        /* The images are bigger than the tile as they contain the isometric extension in the z-axis */
+        var image_width = tile_width + tile_layer_offset_x;
+        var image_height = tile_height + tile_layer_offset_y;
+
+        /* Render the tiles */
+        if (!render_indexes && (tile_textures == null || tile_texture_width != image_width || tile_texture_height != image_height))
+        {
+            tile_texture_width = image_width;
+            tile_texture_height = image_height;
+            tile_textures = theme.render ((int) (image_width * 43), image_height * 2);
+            if (tile_textures == null)
+                return;
+        }
+
+        /* This works because of the way the tiles are sorted. We could
+         * reverse them to make this look a little nicer, but when searching
+         * for a tile we want it the other way around. */
+
+        foreach (var tile in game.tiles)
+        {
+            if (!tile.visible)
+                continue;
+
+            int x, y;
+            get_tile_position (tile, out x, out y);
+
+            /* Select image for this tile, or blank image if paused */
+            var texture_x = get_image_offset (tile.number) * image_width;
+            var texture_y = 0;
+            if (paused)
+            {
+                texture_x = get_image_offset (-1) * image_width;
+                texture_y = 0;
+            }
+            else if (tile == game.selected_tile)
+                texture_y = image_height;
+            else if (game.hint_blink_counter % 2 == 1 && (tile == game.hint_tiles[0] || tile == game.hint_tiles[1]))
+                texture_y = image_height;
+
+            if (render_indexes)
+                cr.set_source_rgb (tile.number / 255.0, tile.number / 255.0, tile.number / 255.0);
+            else
+                Gdk.cairo_set_source_pixbuf (cr, tile_textures, x - texture_x, y - texture_y);
+            cr.rectangle (x, y, image_width, image_height);
+            cr.fill ();
+        }
+    }
+
+    private void update_dimensions ()
+    {
+        var width = get_allocated_width ();
+        var height = get_allocated_height ();
+        
+        if (theme == null)
+            return;
+
+        /* Get aspect ratio from theme - contains 43x2 tiles */
+        var aspect = ((double) theme.get_height () / 2) / ((double) theme.get_width () / 43);
+
+        /* Need enough space for the whole map and one unit border */
+        var map_width = game.map.width + 2.0;
+        var map_height = (game.map.height + 2.0) * aspect;
+
+        /* Scale the map to fit */
+        var unit_width = double.min (width / map_width, height / map_height);
+        var unit_height = unit_width * aspect;
+
+        /* The size of one tile is two units wide, and the correct aspect ratio */
+        tile_width = (int) (unit_width * 2);
+        tile_height = (int) (unit_height * 2);
+
+        /* Offset the tiles when on a higher layer (themes must use these hard-coded ratios) */
+        tile_layer_offset_x = tile_width / 7;
+        tile_layer_offset_y = tile_height / 10;
+
+        /* Center the map */
+        x_offset = (int) (width - game.map.width * unit_width) / 2;
+        y_offset = (int) (height - game.map.height * unit_height) / 2;
+    }
+    
+    private void get_tile_position (Tile tile, out int x, out int y)
+    {
+        x = x_offset + tile.slot.x * tile_width / 2 + tile.slot.layer * tile_layer_offset_x;
+        y = y_offset + tile.slot.y * tile_height / 2 - tile.slot.layer * tile_layer_offset_y;
+    }
+
+    private int get_image_offset (int number)
+    {
+        var set = number / 4;
+
+        /* Invalid numbers use the blank tile */
+        if (number < 0 || set >= 36)
+            return 42;
+
+        /* The bonus tiles have different images for each */
+        if (set == 33)
+            return 33 + number % 4;
+        if (set == 35)
+            return 38 + number % 4;
+        /* The white dragons are inbetween the bonus tiles just to be confusing */
+        if (set == 34)
+            return 37;
+
+        /* Everything else is in set order */
+        return set;
+    }
+    
+    private void redraw_tile_cb (Tile tile)
+    {
+        update_dimensions ();
+        int x, y;
+        get_tile_position (tile, out x, out y);
+        queue_draw_area (x, y, tile_texture_width, tile_texture_height);
+    }
+
+    public override bool draw (Cairo.Context cr)
+    {
+        if (game == null)
+            return false;
+
+        Gdk.cairo_set_source_color (cr, background_color);
+        cr.paint ();        
+        draw_game (cr);
+
+        return true;
+    }
+
+    public override bool button_press_event (Gdk.EventButton event)
+    {
+        if (game == null || paused)
+            return false;
+
+        /* Ignore the 2BUTTON and 3BUTTON events. */
+        if (event.type != Gdk.EventType.BUTTON_PRESS)
+            return false;
+
+        /* Get the tile under the square */
+        var tile = find_tile ((uint) event.x, (uint) event.y);
+
+        /* If not a valid tile then ignore the event */
+        if (tile == null || !game.tile_can_move (tile))
+            return true;
+
+        if (event.button == 1)
+        {
+            /* Select first tile */
+            if (game.selected_tile == null)
+            {
+                game.selected_tile = tile;
+                return true;
+            }
+
+            /* Unselect tile by clicking on it again */
+            if (tile == game.selected_tile)
+            {
+                game.selected_tile = null;
+                return true;
+            }
+
+            /* Attempt to match second tile to the selected one */
+            if (game.selected_tile.matches (tile))
+            {
+                game.remove_pair (game.selected_tile, tile);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private Tile? find_tile (uint x, uint y)
+    {
+        /* Render a 1x1 image where the cursor is using a different color for each tile */
+        var surface = new Cairo.ImageSurface (Cairo.Format.RGB24, 1, 1);
+        var cr = new Cairo.Context (surface);
+        cr.set_source_rgba (255, 255, 255, 255);
+        cr.paint ();
+        cr.translate (-x, -y);
+        draw_game (cr, true);
+
+        /* The color value is the tile under the cursor */
+        unowned uchar[] data = surface.get_data ();
+        var number = data[0];
+        foreach (var tile in game.tiles)
+            if (tile.number == number)
+                return tile;
+
+        return null;
+    }
+}
diff --git a/mahjongg/src/game.vala b/mahjongg/src/game.vala
new file mode 100644
index 0000000..9f095b1
--- /dev/null
+++ b/mahjongg/src/game.vala
@@ -0,0 +1,383 @@
+public class Tile
+{
+    public int number;
+    public Slot slot;
+    public bool visible = true;
+    public int move_number;
+
+    public int set
+    {
+        get { return number / 4; }
+    }
+ 
+    public Tile (Slot slot)
+    {
+        this.slot = slot;
+    }
+    
+    public bool matches (Tile tile)
+    {
+        return tile.set == set;
+    }
+}
+
+private static int compare_tiles (Tile a, Tile b)
+{
+    return compare_slots (a.slot, b.slot);
+}
+
+public class Match
+{
+    public Tile tile0;
+    public Tile tile1;
+    
+    public Match (Tile tile0, Tile tile1)
+    {
+        this.tile0 = tile0;
+        this.tile1 = tile1;
+    }
+}
+
+public class Game
+{
+    public Map map;
+    public List<Tile> tiles = null;
+    public Tile? hint_tiles[2];
+
+    public int move_number;
+
+    /* Hint animation */
+    private uint hint_timer = 0;
+    public uint hint_blink_counter = 0;
+
+    public signal void redraw_tile (Tile tile);
+    public signal void moved ();
+
+    public bool started
+    {
+        get { return move_number > 1; }
+    }
+
+    private Tile? _selected_tile = null;
+    public Tile? selected_tile
+    {
+        get { return _selected_tile; }
+        set
+        {
+            if (_selected_tile != null)
+                redraw_tile (_selected_tile);
+            _selected_tile = value;
+            if (value != null)
+                redraw_tile (value);
+        }
+    }
+
+    public int visible_tiles
+    {
+        get
+        {
+            var n = 0;
+            foreach (var tile in tiles)
+                if (tile.visible)
+                    n++;
+            return n;
+        }
+    }
+
+    public uint moves_left
+    {
+        get { return find_matches ().length (); }
+    }
+
+    public bool complete
+    {
+        get { return visible_tiles == 0; }
+    }
+    
+    public bool can_move
+    {
+        get { return moves_left != 0; }
+    }
+
+    public Game (Map map)
+    {
+        this.map = map;
+        move_number = 1;
+
+        /* Create the tiles in the locations required in the map */
+        foreach (var slot in map.slots)
+        {
+            var tile = new Tile (slot);
+            tile.number = 0;
+            tiles.insert_sorted (tile, compare_tiles);
+        }
+
+        /* Come up with a random solution by picking random pairs and assigning them
+         * with a random value.  If end up with an invalid solution, then choose the
+         * next avaiable pair */
+        var n_pairs = (int) tiles.length () / 2;
+        var numbers = new int[n_pairs];
+        for (var i = 0; i < n_pairs; i++)
+            numbers[i] = i*2;
+        for (var i = 0; i < n_pairs; i++)
+        {
+            var n = Random.int_range (i, n_pairs);
+            var t = numbers[i];
+            numbers[i] = numbers[n];
+            numbers[n] = t;
+        }
+        shuffle (numbers);
+        
+        /* Make everything visible again */
+        reset ();
+    }
+
+    private bool shuffle (int[] numbers, int depth = 0)
+    {
+        /* All shuffled */
+        if (depth == tiles.length () / 2)
+            return true;
+
+        var matches = find_matches ();
+        var n_matches = matches.length ();
+
+        /* No matches on this branch, rewind */
+        if (n_matches == 0)
+            return false;
+
+        var n = Random.int_range (0, (int) n_matches);
+        for (var i = 0; i < n_matches; i++)
+        {
+            var match = matches.nth_data ((n + i) % n_matches);
+            match.tile0.number = numbers[depth];
+            match.tile0.visible = false;
+            match.tile1.number = numbers[depth] + 1;
+            match.tile1.visible = false;
+
+            if (shuffle (numbers, depth + 1))
+                return true;
+
+            /* Undo this move */
+            match.tile0.number = 0;
+            match.tile0.visible = true;
+            match.tile1.number = 0;
+            match.tile1.visible = true;
+        }
+
+        return false;
+    }
+
+    public void reset ()
+    {
+        selected_tile = null;
+        set_hint (null, null);
+        foreach (var tile in tiles)
+        {
+            tile.visible = true;
+            tile.move_number = 0;
+        }
+    }
+
+    public void set_hint (Tile? tile0, Tile? tile1)
+    {
+        /* Stop hints */
+        if (tile0 == null && tile1 == null)
+        {
+            hint_blink_counter = 0;
+            hint_timeout_cb ();
+            return;
+        }
+
+        hint_tiles[0] = tile0;
+        hint_tiles[1] = tile1;
+        hint_blink_counter = 6;
+        if (hint_timer != 0)
+            Source.remove (hint_timer);
+        hint_timer = Timeout.add (250, hint_timeout_cb);
+        hint_timeout_cb ();
+    }
+
+    private bool hint_timeout_cb ()
+    {
+        if (hint_blink_counter == 0)
+        {
+            if (hint_timer != 0)
+                Source.remove (hint_timer);
+            hint_timer = 0;
+            return false;
+        }
+        hint_blink_counter--;
+
+        if (hint_tiles[0] != null)
+            redraw_tile (hint_tiles[0]);
+        if (hint_tiles[1] != null)
+            redraw_tile (hint_tiles[1]);
+
+        return true;
+    }
+
+    public bool tile_can_move (Tile tile)
+    {
+        if (!tile.visible)
+            return false;
+
+        var blocked_left = false;
+        var blocked_right = false;
+        var slot = tile.slot;
+        foreach (var t in tiles)
+        {
+            if (t == tile || !t.visible)
+                continue;
+
+            var s = t.slot;
+
+            /* Can't move if blocked by a tile above */
+            if (s.layer == slot.layer + 1 && 
+                (s.x >= slot.x - 1 && s.x <= slot.x + 1) &&
+                (s.y >= slot.y - 1 && s.y <= slot.y + 1))
+                return false;
+
+            /* Can't move if blocked both on the left and the right */
+            if (s.layer == slot.layer && (s.y >= slot.y - 1 && s.y <= slot.y + 1))
+            {
+                if (s.x == slot.x - 2)
+                    blocked_left = true;
+                if (s.x == slot.x + 2)
+                    blocked_right = true;
+                if (blocked_left && blocked_right)
+                    return false;
+            }
+        }
+
+        return true;
+    }
+
+    public List<Match> find_matches (Tile? tile = null)
+    {
+        List<Match> matches = null;
+
+        if (tile != null && !tile_can_move (tile))
+            return matches;
+
+        if (tile == null)
+        {
+            foreach (var t in tiles)
+            {
+                foreach (var match in find_matches (t))
+                    matches.append (match);
+            }
+        }
+        else
+        {
+            foreach (var t in tiles)
+            {
+                if (t == tile || !tile_can_move (t))
+                    continue;
+
+                if (!t.matches (tile))
+                    continue;
+
+                var already_matched = false;
+                foreach (var match in matches)
+                {
+                    if (match.tile0 == tile && match.tile1 == t)
+                    {
+                        already_matched = true;
+                        break;
+                    }
+                }
+
+                if (!already_matched)
+                    matches.append (new Match (t, tile));
+            }       
+        }
+
+        return matches;
+    }
+
+    public bool remove_pair (Tile tile0, Tile tile1)
+    {
+        if (!tile0.visible || !tile1.visible)
+            return false;
+
+        if (tile0.set != tile1.set)
+            return false;
+
+        selected_tile = null;
+        set_hint (null, null);
+
+        tile0.visible = false;
+        tile0.move_number = move_number;
+        tile1.visible = false;
+        tile1.move_number = move_number;
+
+        move_number++;
+
+        redraw_tile (tile0);
+        redraw_tile (tile1);
+
+        /* You lose your re-do queue when you make a move */
+        foreach (var tile in tiles)
+            if (tile.move_number >= move_number)
+                tile.move_number = 0;
+
+        moved ();
+
+        return true;
+    }
+    
+    public bool can_undo
+    {
+        get { return move_number > 1; }
+    }
+
+    public void undo ()
+    {
+        if (!can_undo)
+            return;
+
+        selected_tile = null;
+        set_hint (null, null);
+
+        /* Re-show tiles that were removed */
+        move_number--;
+        foreach (var tile in tiles)
+        {
+            if (tile.move_number == move_number)
+            {
+                tile.visible = true;
+                redraw_tile (tile);
+            }
+        }
+    }
+
+    public bool can_redo
+    {
+        get
+        {
+            foreach (var tile in tiles)
+                if (tile.move_number >= move_number)
+                    return true;
+            return false;
+        }
+    }
+
+    public void redo ()
+    {
+        if (!can_redo)
+            return;
+
+        selected_tile = null;
+        set_hint (null, null);
+
+        foreach (var tile in tiles)
+        {
+            if (tile.move_number == move_number)
+            {
+                tile.visible = false;
+                redraw_tile (tile);
+            }
+        }
+        move_number++;
+    }
+}
diff --git a/mahjongg/src/mahjongg.vala b/mahjongg/src/mahjongg.vala
new file mode 100644
index 0000000..7b19cee
--- /dev/null
+++ b/mahjongg/src/mahjongg.vala
@@ -0,0 +1,799 @@
+public class Mahjongg
+{
+    private Settings settings;
+
+    private GnomeGamesSupport.Scores highscores;
+
+    private List<Map> maps = null;
+
+    private Gtk.Window window;
+    private GameView game_view;
+    private Gtk.Statusbar statusbar;
+    private Gtk.UIManager ui_manager;
+    private Gtk.Label tiles_label;
+    private Gtk.Toolbar toolbar;
+    private Gtk.Label moves_label;
+    private GnomeGamesSupport.Clock game_clock;
+    private Gtk.Dialog? preferences_dialog = null;
+
+    private GnomeGamesSupport.PauseAction pause_action;
+    private Gtk.Action hint_action;
+    private Gtk.Action redo_action;
+    private Gtk.Action undo_action;
+    private Gtk.Action restart_action;
+    private GnomeGamesSupport.FullscreenAction fullscreen_action;
+    private GnomeGamesSupport.FullscreenAction leave_fullscreen_action;
+
+    public Mahjongg ()
+    {
+        settings = new Settings ("org.gnome.mahjongg");
+
+        load_maps ();
+
+        highscores = new GnomeGamesSupport.Scores ("mahjongg",
+                                                   new GnomeGamesSupport.ScoresCategory[0],
+                                                   null, null, 0,
+                                                   GnomeGamesSupport.ScoreStyle.TIME_ASCENDING);
+        foreach (var map in maps)
+        {
+            var display_name = dpgettext2 (null, "mahjongg map name", map.name);
+            highscores.add_category (map.score_name, display_name);
+        }
+
+        window = new Gtk.Window (Gtk.WindowType.TOPLEVEL);
+        window.title = _("Mahjongg");
+        window.set_default_size (530, 440);
+        GnomeGamesSupport.settings_bind_window_state ("/org/gnome/mahjongg/", window);
+
+        var status_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 10);
+
+        var group_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+        var label = new Gtk.Label (_("Tiles Left:"));
+        group_box.pack_start (label, false, false, 0);
+        var spacer = new Gtk.Label (" ");
+        group_box.pack_start (spacer, false, false, 0);
+        tiles_label = new Gtk.Label ("");
+        group_box.pack_start (tiles_label, false, false, 0);
+        status_box.pack_start (group_box, false, false, 0);
+
+        group_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+        label = new Gtk.Label (_("Moves Left:"));
+        group_box.pack_start (label, false, false, 0);
+        spacer = new Gtk.Label (" ");
+        group_box.pack_start (spacer, false, false, 0);
+        moves_label = new Gtk.Label ("");
+        group_box.pack_start (moves_label, false, false, 0);
+        status_box.pack_start (group_box, false, false, 0);
+
+        group_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+        var game_clock_label = new Gtk.Label (_("Time:"));
+        group_box.pack_start (game_clock_label, false, false, 0);
+        spacer = new Gtk.Label (" ");
+        group_box.pack_start (spacer, false, false, 0);
+        game_clock = new GnomeGamesSupport.Clock ();
+        group_box.pack_start (game_clock, false, false, 0);
+        status_box.pack_start (group_box, false, false, 0);
+
+        /* show the status bar items */
+        statusbar = new Gtk.Statusbar ();
+        ui_manager = new Gtk.UIManager ();
+
+        GnomeGamesSupport.stock_prepare_for_statusbar_tooltips (ui_manager, statusbar);
+
+        create_menus (ui_manager);
+        window.add_accel_group (ui_manager.get_accel_group ());
+        var box = ui_manager.get_widget ("/MainMenu");
+
+        window.delete_event.connect (window_delete_event_cb);
+
+        game_view = new GameView ();
+        game_view.set_size_request (320, 200);
+
+        toolbar = (Gtk.Toolbar) ui_manager.get_widget ("/Toolbar");
+        toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_PRIMARY_TOOLBAR);
+
+        var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+
+        vbox.pack_start (box, false, false, 0);
+        vbox.pack_start (toolbar, false, false, 0);
+        vbox.pack_start (game_view, true, true, 0);
+
+        statusbar.pack_end (status_box, false, false, 6);
+        vbox.pack_end (statusbar, false, false, 0);
+
+        window.add (vbox);
+
+        settings.changed.connect (conf_value_changed_cb);
+
+        new_game ();
+
+        game_view.grab_focus ();
+    }
+
+    public void start ()
+    {
+        window.show_all ();
+
+        leave_fullscreen_action.set_visible_policy (GnomeGamesSupport.VisiblePolicy.ON_FULLSCREEN);
+        conf_value_changed_cb (settings, "tileset");
+        conf_value_changed_cb (settings, "bgcolour");
+        conf_value_changed_cb (settings, "show-toolbar");
+    }
+
+    private void update_ui ()
+    {
+        pause_action.sensitive = game_view.game.move_number > 1;
+        restart_action.sensitive = game_view.game.move_number > 1;
+
+        if (game_view.paused)
+        {
+            hint_action.sensitive = false;
+            undo_action.sensitive = false;
+            redo_action.sensitive = false;
+        }
+        else
+        {
+            hint_action.sensitive = game_view.game.moves_left > 0;
+            undo_action.sensitive = game_view.game.can_undo;
+            redo_action.sensitive = game_view.game.can_redo;
+        }
+
+        moves_label.set_text ("%2u".printf (game_view.game.moves_left));
+        tiles_label.set_text ("%3d".printf (game_view.game.visible_tiles));
+    }
+
+    private void theme_changed_cb (Gtk.ComboBox widget)
+    {
+        Gtk.TreeIter iter;
+        widget.get_active_iter (out iter);
+        string theme;
+        widget.model.get (iter, 1, out theme);
+        settings.set_string ("tileset", theme);
+    }
+
+    private void conf_value_changed_cb (Settings settings, string key)
+    {
+        if (key == "tileset")
+        {
+            var theme = settings.get_string ("tileset");
+            game_view.theme = load_theme_texture (theme);
+            if (game_view.theme == null)
+            {
+                warning ("Unable to load theme %s, falling back to default", theme);
+                game_view.theme = load_theme_texture ("postmodern.svg", true);
+            }
+        }
+        else if (key == "show-toolbar")
+        {
+            toolbar.visible = settings.get_boolean ("show-toolbar");
+        }
+        else if (key == "bgcolour")
+        {
+            game_view.set_background (settings.get_string ("bgcolour"));
+        }
+        else if (key == "mapset")
+        {
+            /* Prompt user if already made a move */
+            if (game_view.game.started)
+            {
+                var dialog = new Gtk.MessageDialog (window,
+                                                    Gtk.DialogFlags.MODAL,
+                                                    Gtk.MessageType.QUESTION,
+                                                    Gtk.ButtonsType.NONE,
+                                                    "%s", _("Do you want to start a new game with this map?"));
+                dialog.format_secondary_text (_("If you continue playing the next game will use the new map."));
+                dialog.add_buttons (_("_Continue playing"), Gtk.ResponseType.REJECT,
+                                    _("Use _new map"), Gtk.ResponseType.ACCEPT,
+                                    null);
+                dialog.set_default_response (Gtk.ResponseType.ACCEPT);
+                var response = dialog.run ();
+                if (response == Gtk.ResponseType.ACCEPT)
+                    new_game ();
+                dialog.destroy ();
+            }
+            else
+                new_game ();
+        }
+    }
+
+    private GnomeGamesSupport.Preimage? load_theme_texture (string filename, bool fail_on_error = false)
+    {
+        var pixmap_directory = GnomeGamesSupport.runtime_get_directory (GnomeGamesSupport.RuntimeDirectory.GAME_PIXMAP_DIRECTORY);
+        var path = Path.build_filename (pixmap_directory, filename);
+        try
+        {
+            return new GnomeGamesSupport.Preimage.from_file (path);
+        }
+        catch (Error e)
+        {
+            warning ("Failed to load theme %s: %s", filename, path);
+            return null;
+        }
+    }
+
+    private void show_toolbar_cb (Gtk.Action action)
+    {
+        var toggle_action = (Gtk.ToggleAction) action;
+        settings.set_boolean ("show-toolbar", toggle_action.active);
+    }
+
+    private void background_changed_cb (Gtk.ColorButton widget)
+    {
+        Gdk.Color colour;
+        widget.get_color (out colour);
+        settings.set_string ("bgcolour", "#%04x%04x%04x".printf (colour.red, colour.green, colour.blue));
+    }
+
+    private void map_changed_cb (Gtk.ComboBox widget)
+    {
+        settings.set_string ("mapset", maps.nth_data (widget.active).name);
+    }
+
+    private void moved_cb ()
+    {
+        /* Start game once moved */
+        if (game_clock.get_seconds () == 0)
+            game_clock.start ();
+
+        update_ui ();
+
+        if (game_view.game.complete)
+        {
+            game_clock.stop ();
+
+            var seconds = game_clock.get_seconds ();
+
+            var p = highscores.add_time_score ((seconds / 60) * 1.0 + (seconds % 60) / 100.0);
+            var scores_dialog = new GnomeGamesSupport.ScoresDialog (window, highscores, _("Mahjongg Scores"));
+            scores_dialog.set_category_description (_("Map:"));
+            var title = _("Puzzle solved!");
+            var message = _("You didn't make the top ten, better luck next time.");
+            if (p == 1)
+                message = _("Your score is the best!");
+            else if (p > 1)
+                message = _("Your score has made the top ten.");
+            scores_dialog.set_message ("<b>%s</b>\n\n%s".printf (title, message));
+            scores_dialog.set_buttons (GnomeGamesSupport.ScoresButtons.QUIT_BUTTON | GnomeGamesSupport.ScoresButtons.NEW_GAME_BUTTON);
+            if (p > 0)
+                scores_dialog.set_hilight (p);
+
+            switch (scores_dialog.run ())
+            {
+            case Gtk.ResponseType.REJECT:
+                Gtk.main_quit ();
+                break;
+            default:
+                new_game ();
+                break;
+            }
+            scores_dialog.destroy ();
+        }
+        else if (!game_view.game.can_move)
+        {
+            var dialog = new Gtk.MessageDialog (window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                                Gtk.MessageType.INFO,
+                                                Gtk.ButtonsType.NONE,
+                                                "%s", _("There are no more moves."));
+            dialog.format_secondary_text (_("Each puzzle has at least one solution.  You can undo your moves and try and find the solution for a time penalty, restart this game or start an new one."));
+            dialog.add_buttons (Gtk.Stock.UNDO, Gtk.ResponseType.REJECT,
+                                _("_Restart"), Gtk.ResponseType.CANCEL,
+                                _("_New game"), Gtk.ResponseType.ACCEPT);
+
+            dialog.set_default_response (Gtk.ResponseType.ACCEPT);
+            switch (dialog.run ())
+            {
+            case Gtk.ResponseType.REJECT:
+                undo_cb ();
+                break;
+            case Gtk.ResponseType.CANCEL:
+                restart_game ();
+                break;
+            default:
+            case Gtk.ResponseType.ACCEPT:
+                new_game ();
+                break;
+            }
+            dialog.destroy ();
+        }
+    }
+
+    private void properties_cb ()
+    {
+        if (preferences_dialog != null)
+        {
+            preferences_dialog.present ();
+            return;
+        }
+
+        preferences_dialog = new Gtk.Dialog.with_buttons (_("Mahjongg Preferences"),
+                                                   window,
+                                                   Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                                   Gtk.Stock.CLOSE,
+                                                   Gtk.ResponseType.CLOSE, null);
+        preferences_dialog.set_border_width (5);
+        var dialog_content_area = (Gtk.Box) preferences_dialog.get_content_area ();
+        dialog_content_area.set_spacing (2);
+        preferences_dialog.set_resizable (false);
+        preferences_dialog.set_default_response (Gtk.ResponseType.CLOSE);
+        preferences_dialog.response.connect (preferences_dialog_response_cb);
+
+        var top_table = new Gtk.Table (4, 1, false);
+        top_table.border_width = 5;
+        top_table.set_row_spacings (18);
+        top_table.set_col_spacings (0);
+
+        var frame = new GnomeGamesSupport.Frame (_("Tiles"));
+        top_table.attach_defaults (frame, 0, 1, 0, 1);
+
+        var table = new Gtk.Table (2, 2, false);
+        table.set_row_spacings (6);
+        table.set_col_spacings (12);
+
+        var label = new Gtk.Label.with_mnemonic (_("_Tile set:"));
+        label.set_alignment (0, 0.5f);
+        table.attach (label, 0, 1, 0, 1, Gtk.AttachOptions.FILL, 0, 0, 0);
+
+        var themes = load_themes ();
+        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);
+        foreach (var theme in themes)
+        {
+            var tokens = theme.split (".", -1);
+            var name = tokens[0];
+
+            Gtk.TreeIter iter;
+            theme_store.append (out iter);
+            theme_store.set (iter, 0, name, 1, theme, -1);
+
+            if (theme == settings.get_string ("tileset"))
+                theme_combo.set_active_iter (iter);
+        }
+        theme_combo.changed.connect (theme_changed_cb);
+        table.attach_defaults (theme_combo, 1, 2, 0, 1);
+        label.set_mnemonic_widget (theme_combo);
+
+        frame.add (table);
+
+        frame = new GnomeGamesSupport.Frame (_("Maps"));
+        top_table.attach_defaults (frame, 0, 1, 1, 2);
+
+        table = new Gtk.Table (1, 2, false);
+        table.set_row_spacings (6);
+        table.set_col_spacings (12);
+
+        label = new Gtk.Label.with_mnemonic (_("_Select map:"));
+        label.set_alignment (0, 0.5f);
+        table.attach (label, 0, 1, 0, 1, Gtk.AttachOptions.FILL, 0, 0, 0);
+
+        var map_combo = new Gtk.ComboBox ();
+        var map_store = new Gtk.ListStore (2, typeof (string), typeof (string));
+        map_combo.model = map_store;
+        renderer = new Gtk.CellRendererText ();
+        map_combo.pack_start (renderer, true);
+        map_combo.add_attribute (renderer, "text", 0);
+        foreach (var map in maps)
+        {
+            var display_name = dpgettext2 (null, "mahjongg map name", map.name);
+
+            Gtk.TreeIter iter;
+            map_store.append (out iter);
+            map_store.set (iter, 0, display_name, 1, map, -1);
+
+            if (settings.get_string ("mapset") == map.name)
+                map_combo.set_active_iter (iter);
+        }
+        map_combo.changed.connect (map_changed_cb);
+        table.attach_defaults (map_combo, 1, 2, 0, 1);
+        label.set_mnemonic_widget (map_combo);
+
+        frame.add (table);
+
+        frame = new GnomeGamesSupport.Frame (_("Colors"));
+        top_table.attach_defaults (frame, 0, 1, 2, 3);
+
+        table = new Gtk.Table (1, 2, false);
+        table.set_row_spacings (6);
+        table.set_col_spacings (12);
+
+        label = new Gtk.Label.with_mnemonic (_("_Background color:"));
+        label.set_alignment (0, 0.5f);
+        table.attach (label, 0, 1, 0, 1, Gtk.AttachOptions.FILL, 0, 0, 0);
+
+        var widget = new Gtk.ColorButton ();
+        widget.set_color (game_view.background_color);
+        widget.color_set.connect (background_changed_cb);
+        table.attach_defaults (widget, 1, 2, 0, 1);
+        label.set_mnemonic_widget (widget);
+
+        frame.add (table);
+
+        dialog_content_area.pack_start (top_table, true, true, 0);
+
+        preferences_dialog.show_all ();
+    }
+
+    private void preferences_dialog_response_cb (Gtk.Dialog dialog, int response)
+    {
+        preferences_dialog.destroy ();
+        preferences_dialog = null;
+    }
+
+    private List<string> load_themes ()
+    {
+        List<string> themes = null;
+
+        var path = GnomeGamesSupport.runtime_get_directory (GnomeGamesSupport.RuntimeDirectory.GAME_PIXMAP_DIRECTORY);
+        Dir dir;
+        try
+        {
+            dir = Dir.open (path);
+        }
+        catch (FileError e)
+        {
+            return themes;
+        }
+
+        while (true)
+        {
+            var s = dir.read_name ();
+            if (s == null)
+                break;
+
+            if (s.has_suffix (".xpm") || s.has_suffix (".svg") || s.has_suffix (".gif") ||
+                s.has_suffix (".png") || s.has_suffix (".jpg") || s.has_suffix (".xbm"))
+                themes.append (s);
+        }
+
+        return themes;
+    }
+
+    private void hint_cb ()
+    {
+        var matches = game_view.game.find_matches (game_view.game.selected_tile);
+        var n_matches = matches.length ();
+
+        /* No match, just flash the selected tile */
+        if (n_matches == 0)
+        {
+            if (game_view.game.selected_tile == null)
+                return;
+            game_view.game.set_hint (game_view.game.selected_tile, null);
+        }
+        else
+        {
+            var n = Random.int_range (0, (int) n_matches);
+            var match = matches.nth_data (n);
+            game_view.game.set_hint (match.tile0, match.tile1);
+        }
+
+        /* 30s penalty */
+        game_clock.start ();
+        game_clock.add_seconds (30);
+    }
+
+    private void about_cb ()
+    {
+        string[] authors =
+        {
+            _("Main game:"),
+            "Francisco Bustamante",
+            "Max Watson",
+            "Heinz Hempe",
+            "Michael Meeks",
+            "Philippe Chavin",
+            "Callum McKenzie",
+            "Robert Ancell",
+            "",
+            _("Maps:"),
+            "Rexford Newbould",
+            "Krzysztof Foltman",
+            null
+        };
+
+        string[] artists =
+        {
+            _("Tiles:"),
+            "Jonathan Buzzard",
+            "Jim Evans",
+            "Richard Hoelscher",
+            "Gonzalo Odiard",
+            "Max Watson",
+            null
+        };
+
+        string[] documenters =
+        {
+            "Eric Baudais",
+            null
+        };
+
+        Gtk.show_about_dialog (window,
+                               "program-name", _("Mahjongg"),
+                               "version", VERSION,
+                               "comments",
+                               _("A matching game played with Mahjongg tiles.\n\nMahjongg is a part of GNOME GnomeGamesSupport."),
+                               "copyright", "Copyright \xc2\xa9 1998-2008 Free Software Foundation, Inc.",
+                               "license", GnomeGamesSupport.get_license (_("Mahjongg")),
+                               "wrap-license", true,
+                               "authors", authors,
+                               "artists", artists,
+                               "documenters", documenters,
+                               "translator-credits", _("translator-credits"),
+                               "logo-icon-name", "gnome-mahjongg",
+                               "website", "http://www.gnome.org/projects/gnome-games";,
+                               "website-label", _("GNOME Games web site"),
+                               null);
+    }
+
+    private void pause_cb (GnomeGamesSupport.PauseAction action)
+    {
+        game_view.paused = action.get_is_paused ();
+        game_view.game.set_hint (null, null);
+        game_view.game.selected_tile = null;
+
+        if (game_view.paused)
+            game_clock.stop ();
+        else
+            game_clock.start ();
+
+        update_ui ();
+    }
+
+    private void scores_cb (Gtk.Action action)
+    {
+        var map_scores_dialog = new GnomeGamesSupport.ScoresDialog (window, highscores, _("Mahjongg Scores"));
+        map_scores_dialog.set_category_description (_("Map:"));
+        map_scores_dialog.run ();
+        map_scores_dialog.destroy ();
+    }
+
+    private void new_game_cb (Gtk.Action action)
+    {
+        new_game ();
+    }
+
+    private void restart_game_cb (Gtk.Action action)
+    {
+        game_view.game.reset ();
+        game_view.queue_draw ();
+    }
+
+    private bool window_delete_event_cb (Gdk.EventAny event)
+    {
+        Gtk.main_quit ();
+        return true;
+    }
+
+    private void quit_cb ()
+    {
+        Gtk.main_quit ();
+    }
+
+    private void redo_cb (Gtk.Action action)
+    {
+        if (game_view.paused)
+            return;
+
+        game_view.game.redo ();
+        update_ui ();
+    }
+
+    private void undo_cb ()
+    {
+        game_view.game.undo ();
+        update_ui ();
+    }
+
+    private void restart_game ()
+    {
+        game_view.game.reset ();
+
+        /* Prepare clock */
+        game_clock.stop ();
+        game_clock.reset ();
+
+        update_ui ();
+    }
+
+    private void new_game ()
+    {
+        Map? map = null;
+        foreach (var m in maps)
+        {
+            if (m.name == settings.get_string ("mapset"))
+            {
+                map = m;
+                break;
+            }
+        }
+        if (map == null)
+            map = maps.nth_data (0);
+
+        game_view.game = new Game (map);
+        game_view.game.moved.connect (moved_cb);
+        highscores.set_category (game_view.game.map.score_name);
+
+        /* Set window title */
+        var display_name = dpgettext2 (null, "mahjongg map name", game_view.game.map.name);
+        /* Translators: This is the window title for Mahjongg which contains the map name, e.g. 'Mahjongg - Red Dragon' */
+        window.set_title (_("Mahjongg - %s").printf (display_name));
+
+        /* Prepare clock */
+        game_clock.stop ();
+        game_clock.reset ();
+
+        update_ui ();
+    }
+
+    private void help_cb (Gtk.Action action)
+    {
+        GnomeGamesSupport.help_display (window, "mahjongg", null);
+    }
+
+    private const Gtk.ActionEntry actions[] =
+    {
+        {"GameMenu", null, N_("_Game")},
+        {"SettingsMenu", null, N_("_Settings")},
+        {"HelpMenu", null, N_("_Help")},
+        {"NewGame", GnomeGamesSupport.STOCK_NEW_GAME, null, null, N_("Start a new game"), new_game_cb},
+        {"RestartGame", GnomeGamesSupport.STOCK_RESTART_GAME, null, null, N_("Restart the current game"), restart_game_cb},
+        {"UndoMove", GnomeGamesSupport.STOCK_UNDO_MOVE, null, null, N_("Undo the last move"), undo_cb},
+        {"RedoMove", GnomeGamesSupport.STOCK_REDO_MOVE, null, null, N_("Redo the last move"), redo_cb},
+        {"Hint", GnomeGamesSupport.STOCK_HINT, null, null, N_("Show a hint"), hint_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, properties_cb},
+        {"Contents", GnomeGamesSupport.STOCK_CONTENTS, null, null, null, help_cb},
+        {"About", Gtk.Stock.ABOUT, null, null, null, about_cb}
+    };
+
+    private const Gtk.ToggleActionEntry toggle_actions[] =
+    {
+        {"ShowToolbar", null, N_("_Toolbar"), null, N_("Show or hide the toolbar"), show_toolbar_cb}
+    };
+
+    private const string ui_description =
+      "<ui>" +
+      "  <menubar name='MainMenu'>" +
+      "    <menu action='GameMenu'>" +
+      "      <menuitem action='NewGame'/>" +
+      "      <menuitem action='RestartGame'/>" +
+      "      <menuitem action='PauseGame'/>" +
+      "      <separator/>" +
+      "      <menuitem action='UndoMove'/>" +
+      "      <menuitem action='RedoMove'/>" +
+      "      <menuitem action='Hint'/>" +
+      "      <separator/>" +
+      "      <menuitem action='Scores'/>" +
+      "      <separator/>" +
+      "      <menuitem action='Quit'/>" +
+      "    </menu>" +
+      "    <menu action='SettingsMenu'>" +
+      "      <menuitem action='Fullscreen'/>" +
+      "      <menuitem action='ShowToolbar'/>" +
+      "      <separator/>" +
+      "      <menuitem action='Preferences'/>" +
+      "    </menu>" +
+      "    <menu action='HelpMenu'>" +
+      "      <menuitem action='Contents'/>" +
+      "      <menuitem action='About'/>" +
+      "    </menu>" +
+      "  </menubar>" +
+      "  <toolbar name='Toolbar'>" +
+      "    <toolitem action='NewGame'/>" +
+      "    <toolitem action='UndoMove'/>" +
+      "    <toolitem action='Hint'/>" +
+      "    <toolitem action='PauseGame'/>" +
+      "    <toolitem action='LeaveFullscreen'/>" +
+      "  </toolbar>" +
+      "</ui>";
+
+    private void create_menus (Gtk.UIManager ui_manager)
+    {
+        var action_group = new Gtk.ActionGroup ("group");
+
+        action_group.set_translation_domain (GETTEXT_PACKAGE);
+        action_group.add_actions (actions, this);
+        action_group.add_toggle_actions (toggle_actions, this);
+
+        ui_manager.insert_action_group (action_group, 0);
+        try
+        {
+            ui_manager.add_ui_from_string (ui_description, -1);
+        }
+        catch (Error e)
+        {
+        }
+        restart_action = action_group.get_action ("RestartGame");
+        pause_action = new GnomeGamesSupport.PauseAction ("PauseGame");
+        pause_action.is_important = true;
+        pause_action.state_changed.connect (pause_cb);
+        action_group.add_action_with_accel (pause_action, null);
+        hint_action = action_group.get_action ("Hint");
+        hint_action.is_important = true;
+        undo_action = action_group.get_action ("UndoMove");
+        undo_action.is_important = true;
+        redo_action = action_group.get_action ("RedoMove");
+        var show_toolbar_action = (Gtk.ToggleAction) action_group.get_action ("ShowToolbar");
+
+        fullscreen_action = new GnomeGamesSupport.FullscreenAction ("Fullscreen", window);
+        action_group.add_action_with_accel (fullscreen_action, null);
+
+        leave_fullscreen_action = new GnomeGamesSupport.FullscreenAction ("LeaveFullscreen", window);
+        action_group.add_action_with_accel (leave_fullscreen_action, null);
+
+        show_toolbar_action.active = settings.get_boolean ("show-toolbar");
+    }
+
+    private void load_maps ()
+    {
+        maps = null;
+
+        /* Add the builtin map */
+        maps.append (new Map.builtin ());
+
+        var path = GnomeGamesSupport.runtime_get_directory (GnomeGamesSupport.RuntimeDirectory.GAME_GAMES_DIRECTORY);
+        var filelist = new GnomeGamesSupport.FileList ("*.map", ".", path, null);
+        for (var i = 0; i < filelist.length (); i++)
+        {
+            var filename = filelist.get_nth (i);
+
+            var loader = new MapLoader ();
+            try
+            {
+                loader.load (filename);
+            }
+            catch (Error e)
+            {
+                warning ("Could not load map %s: %s\n", filename, e.message);
+                continue;
+            }
+            foreach (var map in loader.maps)
+                maps.append (map);
+        }
+    }
+
+    public static int main (string[] args)
+    {
+        Gtk.init (ref args);
+
+        var context = new OptionContext ("");
+        context.set_translation_domain (GETTEXT_PACKAGE);
+        context.add_group (Gtk.get_option_group (true));
+
+        try
+        {
+            context.parse (ref args);
+        }
+        catch (Error e)
+        {
+            stdout.printf ("%s\n", e.message);
+            return Posix.EXIT_FAILURE;
+        }
+
+        if (!GnomeGamesSupport.runtime_init ("mahjongg"))
+            return Posix.EXIT_FAILURE;
+#if ENABLE_SETGID
+        GnomeGamesSupport.setgid_io_init ();
+#endif
+        GnomeGamesSupport.stock_init ();
+
+        Environment.set_application_name (_("Mahjongg"));
+        Gtk.Window.set_default_icon_name ("gnome-mahjongg");
+
+        var app = new Mahjongg ();
+        app.start ();
+
+        Gtk.main ();
+
+        Settings.sync();
+
+        GnomeGamesSupport.runtime_shutdown ();
+
+        return Posix.EXIT_SUCCESS;
+    }
+}
diff --git a/mahjongg/src/map.vala b/mahjongg/src/map.vala
new file mode 100644
index 0000000..acc00fb
--- /dev/null
+++ b/mahjongg/src/map.vala
@@ -0,0 +1,358 @@
+public class Slot
+{
+    public int x;
+    public int y;
+    public int layer;
+    
+    public Slot (int x, int y, int layer)
+    {
+        this.x = x;
+        this.y = y;
+        this.layer = layer;
+    }
+}
+
+private static int compare_slots (Slot a, Slot b)
+{
+    /* Sort lowest to highest */
+    var dl = a.layer - b.layer;
+    if (dl != 0)
+        return dl;
+
+    /* Sort diagonally, top left to bottom right */
+    var dx = a.x - b.x;
+    var dy = a.y - b.y;
+    if (dx > dy)
+        return -1;
+    if (dx < dy)
+        return 1;
+
+    return 0;
+}
+
+public class Map
+{
+    public string? name = null;
+    public string? score_name = null;
+    public List<Slot> slots = null;
+
+    public Map.test ()
+    {
+        name = dpgettext2 (null, "mahjongg map name", "Test");
+        score_name = "test";
+        slots.append (new Slot (0, 0, 0));
+        slots.append (new Slot (2, 0, 0));
+        slots.append (new Slot (2, 0, 1));
+        slots.append (new Slot (4, 0, 0));
+        slots.append (new Slot (0, 2, 0));
+        slots.append (new Slot (2, 2, 0));
+        slots.append (new Slot (2, 2, 1));
+        slots.append (new Slot (4, 2, 0));
+    }
+
+    public Map.builtin ()
+    {
+        name = dpgettext2 (null, "mahjongg map name", "Easy");
+        score_name = "easy";
+        slots.append (new Slot (13, 7, 4));
+        slots.append (new Slot (12, 8, 3));
+        slots.append (new Slot (14, 8, 3));
+        slots.append (new Slot (12, 6, 3));
+        slots.append (new Slot (14, 6, 3));
+        slots.append (new Slot (10, 10, 2));
+        slots.append (new Slot (12, 10, 2));
+        slots.append (new Slot (14, 10, 2));
+        slots.append (new Slot (16, 10, 2));
+        slots.append (new Slot (10, 8, 2));
+        slots.append (new Slot (12, 8, 2));
+        slots.append (new Slot (14, 8, 2));
+        slots.append (new Slot (16, 8, 2));
+        slots.append (new Slot (10, 6, 2));
+        slots.append (new Slot (12, 6, 2));
+        slots.append (new Slot (14, 6, 2));
+        slots.append (new Slot (16, 6, 2));
+        slots.append (new Slot (10, 4, 2));
+        slots.append (new Slot (12, 4, 2));
+        slots.append (new Slot (14, 4, 2));
+        slots.append (new Slot (16, 4, 2));
+        slots.append (new Slot (8, 12, 1));
+        slots.append (new Slot (10, 12, 1));
+        slots.append (new Slot (12, 12, 1));
+        slots.append (new Slot (14, 12, 1));
+        slots.append (new Slot (16, 12, 1));
+        slots.append (new Slot (18, 12, 1));
+        slots.append (new Slot (8, 10, 1));
+        slots.append (new Slot (10, 10, 1));
+        slots.append (new Slot (12, 10, 1));
+        slots.append (new Slot (14, 10, 1));
+        slots.append (new Slot (16, 10, 1));
+        slots.append (new Slot (18, 10, 1));
+        slots.append (new Slot (8, 8, 1));
+        slots.append (new Slot (10, 8, 1));
+        slots.append (new Slot (12, 8, 1));
+        slots.append (new Slot (14, 8, 1));
+        slots.append (new Slot (16, 8, 1));
+        slots.append (new Slot (18, 8, 1));
+        slots.append (new Slot (8, 6, 1));
+        slots.append (new Slot (10, 6, 1));
+        slots.append (new Slot (12, 6, 1));
+        slots.append (new Slot (14, 6, 1));
+        slots.append (new Slot (16, 6, 1));
+        slots.append (new Slot (18, 6, 1));
+        slots.append (new Slot (8, 4, 1));
+        slots.append (new Slot (10, 4, 1));
+        slots.append (new Slot (12, 4, 1));
+        slots.append (new Slot (14, 4, 1));
+        slots.append (new Slot (16, 4, 1));
+        slots.append (new Slot (18, 4, 1));
+        slots.append (new Slot (8, 2, 1));
+        slots.append (new Slot (10, 2, 1));
+        slots.append (new Slot (12, 2, 1));
+        slots.append (new Slot (14, 2, 1));
+        slots.append (new Slot (16, 2, 1));
+        slots.append (new Slot (18, 2, 1));
+        slots.append (new Slot (2, 14, 0));
+        slots.append (new Slot (4, 14, 0));
+        slots.append (new Slot (6, 14, 0));
+        slots.append (new Slot (8, 14, 0));
+        slots.append (new Slot (10, 14, 0));
+        slots.append (new Slot (12, 14, 0));
+        slots.append (new Slot (14, 14, 0));
+        slots.append (new Slot (16, 14, 0));
+        slots.append (new Slot (18, 14, 0));
+        slots.append (new Slot (20, 14, 0));
+        slots.append (new Slot (22, 14, 0));
+        slots.append (new Slot (24, 14, 0));
+        slots.append (new Slot (6, 12, 0));
+        slots.append (new Slot (8, 12, 0));
+        slots.append (new Slot (10, 12, 0));
+        slots.append (new Slot (12, 12, 0));
+        slots.append (new Slot (14, 12, 0));
+        slots.append (new Slot (16, 12, 0));
+        slots.append (new Slot (18, 12, 0));
+        slots.append (new Slot (20, 12, 0));
+        slots.append (new Slot (4, 10, 0));
+        slots.append (new Slot (6, 10, 0));
+        slots.append (new Slot (8, 10, 0));
+        slots.append (new Slot (10, 10, 0));
+        slots.append (new Slot (12, 10, 0));
+        slots.append (new Slot (14, 10, 0));
+        slots.append (new Slot (16, 10, 0));
+        slots.append (new Slot (18, 10, 0));
+        slots.append (new Slot (20, 10, 0));
+        slots.append (new Slot (22, 10, 0));
+        slots.append (new Slot (0, 7, 0));
+        slots.append (new Slot (2, 8, 0));
+        slots.append (new Slot (4, 8, 0));
+        slots.append (new Slot (6, 8, 0));
+        slots.append (new Slot (8, 8, 0));
+        slots.append (new Slot (10, 8, 0));
+        slots.append (new Slot (12, 8, 0));
+        slots.append (new Slot (14, 8, 0));
+        slots.append (new Slot (16, 8, 0));
+        slots.append (new Slot (18, 8, 0));
+        slots.append (new Slot (20, 8, 0));
+        slots.append (new Slot (22, 8, 0));
+        slots.append (new Slot (24, 8, 0));
+        slots.append (new Slot (2, 6, 0));
+        slots.append (new Slot (4, 6, 0));
+        slots.append (new Slot (6, 6, 0));
+        slots.append (new Slot (8, 6, 0));
+        slots.append (new Slot (10, 6, 0));
+        slots.append (new Slot (12, 6, 0));
+        slots.append (new Slot (14, 6, 0));
+        slots.append (new Slot (16, 6, 0));
+        slots.append (new Slot (18, 6, 0));
+        slots.append (new Slot (20, 6, 0));
+        slots.append (new Slot (22, 6, 0));
+        slots.append (new Slot (24, 6, 0));
+        slots.append (new Slot (4, 4, 0));
+        slots.append (new Slot (6, 4, 0));
+        slots.append (new Slot (8, 4, 0));
+        slots.append (new Slot (10, 4, 0));
+        slots.append (new Slot (12, 4, 0));
+        slots.append (new Slot (14, 4, 0));
+        slots.append (new Slot (16, 4, 0));
+        slots.append (new Slot (18, 4, 0));
+        slots.append (new Slot (20, 4, 0));
+        slots.append (new Slot (22, 4, 0));
+        slots.append (new Slot (6, 2, 0));
+        slots.append (new Slot (8, 2, 0));
+        slots.append (new Slot (10, 2, 0));
+        slots.append (new Slot (12, 2, 0));
+        slots.append (new Slot (14, 2, 0));
+        slots.append (new Slot (16, 2, 0));
+        slots.append (new Slot (18, 2, 0));
+        slots.append (new Slot (20, 2, 0));
+        slots.append (new Slot (2, 0, 0));
+        slots.append (new Slot (4, 0, 0));
+        slots.append (new Slot (6, 0, 0));
+        slots.append (new Slot (8, 0, 0));
+        slots.append (new Slot (10, 0, 0));
+        slots.append (new Slot (12, 0, 0));
+        slots.append (new Slot (14, 0, 0));
+        slots.append (new Slot (16, 0, 0));
+        slots.append (new Slot (18, 0, 0));
+        slots.append (new Slot (20, 0, 0));
+        slots.append (new Slot (22, 0, 0));
+        slots.append (new Slot (24, 0, 0));
+        slots.append (new Slot (26, 7, 0));
+        slots.append (new Slot (28, 7, 0));
+    }
+    
+    public uint width
+    {
+        get
+        {
+            var w = 0;
+            foreach (var slot in slots)
+            {
+                if (slot.x > w)
+                    w = slot.x;
+            }
+            
+            /* Width is x location of right most tile and the width of that tile (2 units) */
+            return w + 2;
+        }
+    }
+
+    public uint height
+    {
+        get
+        {
+            var h = 0;
+            foreach (var slot in slots)
+            {
+                if (slot.y > h)
+                    h = slot.y;
+            }
+
+            /* Height is x location of bottom most tile and the height of that tile (2 units) */
+            return h + 2;
+        }
+    }
+}
+
+public class MapLoader
+{
+    public List<Map> maps = null;
+    private Map map;
+    private int layer_z = 0;
+
+    public void load (string filename) throws Error
+    {
+        string data;
+        size_t length;
+        FileUtils.get_contents (filename, out data, out length);
+
+        var parser = MarkupParser ();
+        parser.start_element = start_element_cb;
+        parser.end_element = end_element_cb;
+        parser.text = null;
+        parser.passthrough = null;
+        parser.error = null;
+        var parse_context = new MarkupParseContext (parser, 0, this, null);
+        try
+        {
+            parse_context.parse (data, (ssize_t) length);
+        }
+        catch (MarkupError e)
+        {
+        }
+    }
+
+    private string? get_attribute (string[] attribute_names, string[] attribute_values, string name, string? default = null)
+    {
+        for (var i = 0; attribute_names[i] != null; i++)
+        {
+            if (attribute_names[i].down() == name)
+                return attribute_values[i];
+        }
+
+        return default;
+    }
+
+    private double get_attribute_d (string[] attribute_names, string[] attribute_values, string name, double default = 0.0)
+    {
+        var a = get_attribute (attribute_names, attribute_values, name);
+        if (a == null)
+            return default;
+        else
+            return double.parse (a);
+    }
+
+    private void start_element_cb (MarkupParseContext context, string element_name, string[] attribute_names, string[] attribute_values) throws MarkupError
+    {
+        /* Identify the tag. */
+        switch (element_name.down ())
+        {
+        case "mahjongg":
+            break;
+
+        case "map":
+            map = new Map ();
+            map.name = get_attribute (attribute_names, attribute_values, "name", "");
+            map.score_name = get_attribute (attribute_names, attribute_values, "scorename", "");
+            break;
+
+        case "layer":
+            layer_z = (int) get_attribute_d (attribute_names, attribute_values, "z");
+            break;
+
+        case "row":
+            var x1 = (int) (get_attribute_d (attribute_names, attribute_values, "left") * 2);
+            var x2 = (int) (get_attribute_d (attribute_names, attribute_values, "right") * 2);
+            var y = (int) (get_attribute_d (attribute_names, attribute_values, "y") * 2);
+            var z = (int) get_attribute_d (attribute_names, attribute_values, "z", layer_z);
+            for (; x1 <= x2; x1 += 2)
+                map.slots.append (new Slot (x1, y, z));
+            break;
+
+        case "column":
+            var x = (int) (get_attribute_d (attribute_names, attribute_values, "x") * 2);
+            var y1 = (int) (get_attribute_d (attribute_names, attribute_values, "top") * 2);
+            var y2 = (int) (get_attribute_d (attribute_names, attribute_values, "bottom") * 2);
+            var z = (int) get_attribute_d (attribute_names, attribute_values, "z", layer_z);
+            for (; y1 <= y2; y1 += 2)
+                map.slots.append (new Slot (x, y1, z));
+            break;
+
+        case "block":
+            var x1 = (int) (get_attribute_d (attribute_names, attribute_values, "left") * 2);
+            var x2 = (int) (get_attribute_d (attribute_names, attribute_values, "right") * 2);
+            var y1 = (int) (get_attribute_d (attribute_names, attribute_values, "top") * 2);
+            var y2 = (int) (get_attribute_d (attribute_names, attribute_values, "bottom") * 2);
+            var z = (int) get_attribute_d (attribute_names, attribute_values, "z", layer_z);
+            for (; x1 <= x2; x1 += 2)
+                for (var y = y1; y <= y2; y += 2)
+                    map.slots.append (new Slot (x1, y, z));
+            break;
+
+        case "tile":
+            var x = (int) (get_attribute_d (attribute_names, attribute_values, "x") * 2);
+            var y = (int) (get_attribute_d (attribute_names, attribute_values, "y") * 2);
+            var z = (int) get_attribute_d (attribute_names, attribute_values, "z", layer_z);
+            map.slots.append (new Slot (x, y, z));
+            break;
+        }
+    }
+
+    private void end_element_cb (MarkupParseContext context, string element_name) throws MarkupError
+    {
+        switch (element_name.down ())
+        {
+        case "map":
+            var n_slots = map.slots.length ();
+            if (map.name != null && map.score_name != null && n_slots <= 144 && n_slots % 2 == 0)
+                maps.append (map);
+            else
+                warning ("Invalid map");
+            map = null;
+            break;
+
+        case "layer":
+            layer_z = 0;
+            break;
+        }
+    }
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e151791..f39954e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -121,12 +121,13 @@ lightsoff/lightsoff.schemas.in
 [type: gettext/glade]lightsoff/data/lightsoff.ui
 [type: gettext/glade]lightsoff/data/settings.ui
 lightsoff/src/About.js
-mahjongg/drawing.c
-mahjongg/mahjongg.c
-mahjongg/mahjongg.desktop.in.in
-mahjongg/org.gnome.mahjongg.gschema.xml.in
-mahjongg/maps.c
-mahjongg/translatable_game_names.h
+mahjongg/src/game.vala
+mahjongg/src/game-view.vala
+mahjongg/src/mahjongg.vala
+mahjongg/src/map.vala
+mahjongg/data/mahjongg.desktop.in.in
+mahjongg/data/org.gnome.mahjongg.gschema.xml.in
+mahjongg/data/translatable_game_names.h
 [type: gettext/glade]swell-foop/data/swell-foop.ui
 [type: gettext/glade]swell-foop/data/settings.ui
 swell-foop/swell-foop.desktop.in.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 766ce0f..1b36745 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -14,6 +14,10 @@ gtali/gtali.desktop.in
 iagno/iagno.desktop.in
 libgames-support/org.gnome.Games.WindowState.gschema.xml.in
 lightsoff/lightsoff.desktop.in
-mahjongg/mahjongg.desktop.in
+mahjongg/src/game.c
+mahjongg/src/game-view.c
+mahjongg/src/mahjongg.c
+mahjongg/src/map.c
+mahjongg/data/mahjongg.desktop.in
 swell-foop/swell-foop-c.desktop.in
 swell-foop/swell-foop.desktop.in



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