[gnome-games] lightsoff: Port from Javascript to Vala



commit af473091374e20dea00bf788a1708d29fd6963d9
Author: Robert Ancell <robert ancell canonical com>
Date:   Sat Nov 19 14:11:21 2011 +1100

    lightsoff: Port from Javascript to Vala

 configure.in                                      |   18 +-
 lightsoff/Makefile.am                             |   41 +---
 lightsoff/data/Makefile.am                        |   41 +++
 lightsoff/data/{themes/tango => }/arrow.svg       |    0
 lightsoff/data/{themes/tango => }/backing.svg     |    0
 lightsoff/data/{themes/tango => }/highlight.svg   |    0
 lightsoff/data/{themes/tango => }/led-back.svg    |    0
 lightsoff/{ => data}/lightsoff.desktop.in.in      |    0
 lightsoff/data/lightsoff.ui                       |   18 +-
 lightsoff/data/{themes/tango => }/off.svg         |    0
 lightsoff/data/{themes/tango => }/on.svg          |    0
 lightsoff/data/org.gnome.lightsoff.gschema.xml.in |    9 +
 lightsoff/data/settings.ui                        |  135 ---------
 lightsoff/data/themes/Makefile.am                 |    3 -
 lightsoff/data/themes/tango/Makefile.am           |   20 --
 lightsoff/data/themes/tango/theme.js              |  136 ---------
 lightsoff/data/themes/up/Makefile.am              |   20 --
 lightsoff/data/themes/up/arrow.svg                |   66 ----
 lightsoff/data/themes/up/backing.svg              |  141 ---------
 lightsoff/data/themes/up/highlight.svg            |   99 -------
 lightsoff/data/themes/up/led-back.svg             |  117 --------
 lightsoff/data/themes/up/off.svg                  |  164 -----------
 lightsoff/data/themes/up/on.svg                   |  198 -------------
 lightsoff/data/themes/up/theme.js                 |   20 --
 lightsoff/lightsoff.schemas.in                    |   43 ---
 lightsoff/src/About.js                            |   29 --
 lightsoff/src/Arrow.js                            |   28 --
 lightsoff/src/Board.js                            |  214 --------------
 lightsoff/src/Game.js                             |  327 ---------------------
 lightsoff/src/LED.js                              |  181 ------------
 lightsoff/src/Light.js                            |   84 ------
 lightsoff/src/Makefile.am                         |   77 ++---
 lightsoff/src/Path.js.in                          |    1 -
 lightsoff/src/Puzzle.js                           |  162 ----------
 lightsoff/src/Settings.js                         |  171 -----------
 lightsoff/src/ThemeLoader.js                      |   44 ---
 lightsoff/src/board-view.vala                     |  238 +++++++++++++++
 lightsoff/src/config.vapi                         |    2 +
 lightsoff/src/fixes.vapi                          |    9 +
 lightsoff/src/game-view.vala                      |  276 +++++++++++++++++
 lightsoff/src/led-array.vala                      |  133 +++++++++
 lightsoff/src/lightsoff.in                        |    5 -
 lightsoff/src/lightsoff.vala                      |  166 +++++++++++
 lightsoff/src/main.js                             |   73 -----
 lightsoff/src/puzzle-generator.vala               |  198 +++++++++++++
 po/POTFILES.in                                    |   11 +-
 46 files changed, 1125 insertions(+), 2593 deletions(-)
---
diff --git a/configure.in b/configure.in
index a5dc6bc..fa6f247 100644
--- a/configure.in
+++ b/configure.in
@@ -22,9 +22,9 @@ AM_MAINTAINER_MODE([enable])
 # we support and which features to check for
 
 # This is the canonical list of all game subdirectories.
-allgames="glchess glines gnect gnibbles gnobots2 gnomine gnotravex gnotski gtali iagno mahjongg quadrapassel gnome-sudoku"
+allgames="glchess glines gnect gnibbles gnobots2 gnomine gnotravex gnotski gtali iagno lightsoff mahjongg quadrapassel gnome-sudoku"
 AC_SUBST([allgames])
-staginggames="lightsoff swell-foop"
+staginggames="swell-foop"
 AC_SUBST([staginggames])
 
 gamelist=""
@@ -126,7 +126,7 @@ for game in $gamelist; do
     *) ;;
   esac
   case $game in
-    glchess|gnomine|gnotravex|mahjongg) need_vala=yes ;;
+    glchess|gnomine|gnotravex|lightsoff|mahjongg) need_vala=yes ;;
     *) ;;
   esac
   case $game in
@@ -878,6 +878,10 @@ glines/Makefile
 glines/data/Makefile
 glines/src/Makefile
 glines/help/Makefile
+lightsoff/Makefile
+lightsoff/src/Makefile
+lightsoff/data/Makefile
+lightsoff/help/Makefile
 quadrapassel/Makefile
 quadrapassel/data/Makefile
 quadrapassel/help/Makefile
@@ -921,19 +925,13 @@ gnibbles/data/gnibbles.desktop.in
 gnotski/data/gnotski.desktop.in
 glchess/glchess.desktop.in
 glines/data/glines.desktop.in
+lightsoff/data/lightsoff.desktop.in
 mahjongg/data/mahjongg.desktop.in
 gtali/data/gtali.desktop.in
 gnome-sudoku/gnome-sudoku.desktop.in
 iagno/data/iagno.desktop.in
 gnect/data/gnect.desktop.in
 gnomine/data/gnomine.desktop.in
-lightsoff/Makefile
-lightsoff/help/Makefile
-lightsoff/lightsoff.desktop.in
-lightsoff/data/themes/Makefile
-lightsoff/data/themes/tango/Makefile
-lightsoff/data/themes/up/Makefile
-lightsoff/src/Makefile
 ])
 AC_OUTPUT
 
diff --git a/lightsoff/Makefile.am b/lightsoff/Makefile.am
index 81c3d34..904a4c8 100644
--- a/lightsoff/Makefile.am
+++ b/lightsoff/Makefile.am
@@ -1,44 +1,7 @@
-SUBDIRS = data/themes
+SUBDIRS = src data
 
 if BUILD_HELP
-SUBDIRS += src help
+SUBDIRS += help
 endif
 
-lightsoffdir = $(pkgdatadir)/lightsoff
-lightsoff_DATA = \
-	data/settings.ui \
-	data/lightsoff.ui
-
-schema_in_files = lightsoff.schemas.in
-if HAVE_GNOME
-schemadir = $(GCONF_SCHEMA_FILE_DIR)
-schema_DATA = $(schema_in_files:.schemas.in=.schemas)
-endif
-
-desktop_in_files = lightsoff.desktop.in.in
-desktopdir = $(datadir)/applications
-desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
- INTLTOOL_DESKTOP_RULE@
-
-CLEANFILES = $(desktop_DATA) $(schema_DATA)
-DISTCLEANFILES = $(desktop_DATA) $(schema_DATA)
-
-EXTRA_DIST = \
-	data/settings.ui \
-	data/lightsoff.ui \
-	$(schema_in_files)
-
-install-schemas-local: $(schema_DATA)
-if GCONF_SCHEMAS_INSTALL
-	if test -z "$(DESTDIR)" ; then \
-		for p in $^ ; do \
-			GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $$p 2>&1 > /dev/null; \
-		done \
-	fi
-endif
-
-install-data-local: install-schemas-local
-
- INTLTOOL_SCHEMAS_RULE@
-
 -include $(top_srcdir)/git.mk
diff --git a/lightsoff/data/Makefile.am b/lightsoff/data/Makefile.am
new file mode 100644
index 0000000..a3596d2
--- /dev/null
+++ b/lightsoff/data/Makefile.am
@@ -0,0 +1,41 @@
+lightsoffdir = $(pkgdatadir)/lightsoff
+lightsoff_DATA = \
+	lightsoff.ui \
+	arrow.svg \
+	backing.svg \
+	led-back.svg \
+	off.svg \
+	on.svg \
+	highlight.svg
+
+gsettings_in_file = org.gnome.lightsoff.gschema.xml.in
+gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml)
+ INTLTOOL_XML_NOMERGE_RULE@
+ GSETTINGS_RULES@
+
+desktop_in_files = lightsoff.desktop.in.in
+desktopdir = $(datadir)/applications
+desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
+ INTLTOOL_DESKTOP_RULE@
+
+CLEANFILES = $(desktop_DATA) $(schema_DATA)
+DISTCLEANFILES = $(desktop_DATA) $(schema_DATA)
+
+EXTRA_DIST = \
+	$(lightsoff_DATA) \
+	$(schema_in_files)
+
+install-schemas-local: $(schema_DATA)
+if GCONF_SCHEMAS_INSTALL
+	if test -z "$(DESTDIR)" ; then \
+		for p in $^ ; do \
+			GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $$p 2>&1 > /dev/null; \
+		done \
+	fi
+endif
+
+install-data-local: install-schemas-local
+
+ INTLTOOL_SCHEMAS_RULE@
+
+-include $(top_srcdir)/git.mk
diff --git a/lightsoff/data/themes/tango/arrow.svg b/lightsoff/data/arrow.svg
similarity index 100%
rename from lightsoff/data/themes/tango/arrow.svg
rename to lightsoff/data/arrow.svg
diff --git a/lightsoff/data/themes/tango/backing.svg b/lightsoff/data/backing.svg
similarity index 100%
rename from lightsoff/data/themes/tango/backing.svg
rename to lightsoff/data/backing.svg
diff --git a/lightsoff/data/themes/tango/highlight.svg b/lightsoff/data/highlight.svg
similarity index 100%
rename from lightsoff/data/themes/tango/highlight.svg
rename to lightsoff/data/highlight.svg
diff --git a/lightsoff/data/themes/tango/led-back.svg b/lightsoff/data/led-back.svg
similarity index 100%
rename from lightsoff/data/themes/tango/led-back.svg
rename to lightsoff/data/led-back.svg
diff --git a/lightsoff/lightsoff.desktop.in.in b/lightsoff/data/lightsoff.desktop.in.in
similarity index 100%
rename from lightsoff/lightsoff.desktop.in.in
rename to lightsoff/data/lightsoff.desktop.in.in
diff --git a/lightsoff/data/lightsoff.ui b/lightsoff/data/lightsoff.ui
index c9c8b7e..c4cfb7d 100644
--- a/lightsoff/data/lightsoff.ui
+++ b/lightsoff/data/lightsoff.ui
@@ -25,7 +25,7 @@
 										<property name="visible">True</property>
 										<child>
 											<object class="GtkImageMenuItem" id="new_game_item">
-												<signal name="activate" handler="reset_score"/>
+												<signal name="activate" handler="new_game_cb"/>
 												<property name="label">games-new-game</property>
 												<property name="visible">True</property>
 												<property name="use_underline">True</property>
@@ -34,21 +34,11 @@
 											</object>
 										</child>
 										<child>
-											<object class="GtkImageMenuItem" id="show_preferences_item">
-												<signal name="activate" handler="show_settings"/>
-												<property name="label">gtk-preferences</property>
-												<property name="visible">True</property>
-												<property name="use_underline">True</property>
-												<property name="use_stock">True</property>
-												<property name="accel_group">accel_group</property>
-											</object>
-										</child>
-										<child>
 											<object class="GtkSeparatorMenuItem" id="separator1" />
 										</child>
 										<child>
 											<object class="GtkImageMenuItem" id="quit_item">
-												<signal name="activate" handler="quit"/>
+												<signal name="activate" handler="quit_cb"/>
 												<property name="label">gtk-quit</property>
 												<property name="visible">True</property>
 												<property name="use_underline">True</property>
@@ -70,7 +60,7 @@
 										<property name="visible">True</property>
 										<child>
 											<object class="GtkImageMenuItem" id="show_help_item">
-												<signal name="activate" handler="show_help"/>
+												<signal name="activate" handler="help_cb"/>
 												<property name="label">games-contents</property>
 												<property name="visible">True</property>
 												<property name="use_underline">True</property>
@@ -80,7 +70,7 @@
 										</child>
 										<child>
 											<object class="GtkImageMenuItem" id="show_about_item">
-												<signal name="activate" handler="show_about"/>
+												<signal name="activate" handler="about_cb"/>
 												<property name="label">gtk-about</property>
 												<property name="visible">True</property>
 												<property name="use_underline">True</property>
diff --git a/lightsoff/data/themes/tango/off.svg b/lightsoff/data/off.svg
similarity index 100%
rename from lightsoff/data/themes/tango/off.svg
rename to lightsoff/data/off.svg
diff --git a/lightsoff/data/themes/tango/on.svg b/lightsoff/data/on.svg
similarity index 100%
rename from lightsoff/data/themes/tango/on.svg
rename to lightsoff/data/on.svg
diff --git a/lightsoff/data/org.gnome.lightsoff.gschema.xml.in b/lightsoff/data/org.gnome.lightsoff.gschema.xml.in
new file mode 100644
index 0000000..b29fe1a
--- /dev/null
+++ b/lightsoff/data/org.gnome.lightsoff.gschema.xml.in
@@ -0,0 +1,9 @@
+<schemalist>
+  <schema id="org.gnome.lightsoff" path="/org/gnome/lightsoff/">
+    <key name="level" type="i">
+      <default>1</default>
+      <_summary>The current level</_summary>
+      <_description>The users's most recent level.</_description>
+    </key>
+  </schema>
+</schemalist>
diff --git a/lightsoff/src/Makefile.am b/lightsoff/src/Makefile.am
index 6fdcb91..f906233 100644
--- a/lightsoff/src/Makefile.am
+++ b/lightsoff/src/Makefile.am
@@ -1,47 +1,34 @@
-lightsoffdir = $(pkgdatadir)/lightsoff
-
-lightsoff_DATA = \
-	About.js \
-	Arrow.js \
-	Board.js \
-	Light.js \
-	main.js \
-	Path.js \
-	Game.js \
-	LED.js \
-	Puzzle.js \
-	Settings.js \
-	ThemeLoader.js
-
-bin_SCRIPTS = \
-	lightsoff
-
-lightsoff: lightsoff.in Makefile
-	$(AM_V_GEN) $(SED) -e "s|%pkglibdir%|$(pkglibdir)|" -e "s|%pkgdatadir%|$(pkgdatadir)|" $< > $@
-
-Path.js: Path.js.in
-	$(AM_V_GEN) $(SED) -e "s|%pkgdatadir%|$(pkgdatadir)|" $< > $@
-
-EXTRA_DIST = \
-	lightsoff.in \
-	About.js \
-	Arrow.js \
-	Board.js \
-	Light.js \
-	main.js \
-	Path.js.in \
-	Game.js \
-	LED.js \
-	Puzzle.js \
-	Settings.js \
-	ThemeLoader.js
-
-CLEANFILES = \
-	lightsoff \
-	Path.js
-
-DISTCLEANFILES = \
-	lightsoff \
-	Path.js
+bin_PROGRAMS = lightsoff
+
+lightsoff_SOURCES = \
+	board-view.vala \
+	config.vapi \
+	fixes.vapi \
+	lightsoff.vala \
+	led-array.vala \
+	puzzle-generator.vala \
+	game-view.vala
+
+lightsoff_VALAFLAGS = \
+	--pkg posix \
+    --pkg gmodule-2.0 \
+	--pkg clutter-gtk-1.0 \
+	--vapidir $(top_srcdir)/libgames-support \
+	--pkg GnomeGamesSupport-1.0
+
+lightsoff_CFLAGS = \
+	-I$(top_srcdir)/libgames-support \
+	-DVERSION=\"$(VERSION)\" \
+	-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
+	$(GMODULE_CFLAGS) \
+	$(GTK_CFLAGS)	\
+	$(CLUTTER_GTK_CFLAGS)
+
+lightsoff_LDADD =	\
+	$(top_builddir)/libgames-support/libgames-support.la \
+	$(GMODULE_LIBS) \
+	$(GTK_LIBS)	\
+	$(CLUTTER_GTK_LIBS)	\
+	$(INTLLIBS)
 
 -include $(top_srcdir)/git.mk
diff --git a/lightsoff/src/board-view.vala b/lightsoff/src/board-view.vala
new file mode 100644
index 0000000..9a0eedb
--- /dev/null
+++ b/lightsoff/src/board-view.vala
@@ -0,0 +1,238 @@
+private class Light : Clutter.Group
+{
+    private Clutter.Actor off;
+    private Clutter.Actor on;
+
+    private bool _is_lit;
+    public bool is_lit
+    {
+        get { return _is_lit; }
+        set
+        {
+            value = value != false;
+            if (value != _is_lit)
+                toggle ();
+        }
+    }
+
+    public Light (Clutter.Actor off_actor, Clutter.Actor on_actor)
+    {
+        set_scale (0.9, 0.9);
+
+        off = new Clutter.Clone (off_actor);
+        off.anchor_gravity = Clutter.Gravity.CENTER;
+        add_actor (off);
+
+        on = new Clutter.Clone (on_actor);
+        on.anchor_gravity = Clutter.Gravity.CENTER;
+        on.opacity = 0;
+        add_actor (on);
+
+        // Add a 2 px margin around the tile image, center tiles within it.
+        width += 4;
+        height += 4;
+        off.set_position (width / 2, height / 2);
+        on.set_position (width / 2, height / 2);
+    }
+
+    public void toggle (Clutter.Timeline? timeline = null)
+    {
+        _is_lit = !_is_lit;
+
+        if (timeline != null)
+        {
+            // Animate the opacity of the 'off' tile to match the state.
+            off.animate_with_timeline (Clutter.AnimationMode.EASE_OUT_SINE, timeline, "opacity", is_lit ? 0 : 255);
+            on.animate_with_timeline (Clutter.AnimationMode.EASE_OUT_SINE, timeline, "opacity", is_lit ? 255 : 0);
+
+            // Animate the tile to be smaller when in the 'off' state.
+            animate_with_timeline (Clutter.AnimationMode.EASE_OUT_SINE, timeline,
+                                   "scale-x", is_lit ? 1.0 : 0.9,
+                                   "scale-y", is_lit ? 1.0 : 0.9);
+        }
+        else
+        {
+            off.opacity = is_lit ? 0 : 255;
+            on.opacity = is_lit ? 255 : 0;
+            scale_x = is_lit ? 1 : 0.9;
+            scale_y = is_lit ? 1 : 0.9;
+        }
+    }
+}
+
+public class BoardView : Clutter.Group
+{
+    private const int size = 5;
+    private PuzzleGenerator puzzle_generator;
+    private Clutter.Texture off_texture;
+    private Clutter.Texture on_texture;
+    private Light[,] lights;
+
+    public bool playable = true;
+    
+    public signal void game_won ();
+    
+    public BoardView (Clutter.Texture off_texture, Clutter.Texture on_texture)
+    {
+        this.off_texture = off_texture;
+        this.on_texture = on_texture;
+        puzzle_generator = new PuzzleGenerator (size);
+        lights = new Light [size, size];
+        for (var x = 0; x < size; x++)
+        {
+            for (var y = 0; y < size; y++)
+            {
+                var l = new Light (off_texture, on_texture);
+
+                l.reactive = true;
+                l.button_press_event.connect (light_button_press_cb);
+
+                float xx, yy;
+                get_light_position (x, y, out xx, out yy);
+                l.anchor_gravity = Clutter.Gravity.CENTER;
+                l.set_position (xx, yy);
+
+                lights[x, y] = l;
+                add_actor (l);
+            }
+        }
+    }
+
+    public void get_light_position (int x, int y, out float xx, out float yy)
+    {
+        // All lights need to be shifted down and right by half a light,
+        // as lights have center gravity.
+        xx = (x + 0.5f) * off_texture.width + 2;
+        yy = (y + 0.5f) * off_texture.height + 2;
+    }
+    
+    public void fade_in (Clutter.Timeline timeline)
+    {
+        animate_with_timeline (Clutter.AnimationMode.EASE_OUT_SINE, timeline, "opactity", 0);
+    }
+
+    public void fade_out (Clutter.Timeline timeline)
+    {
+        animate_with_timeline (Clutter.AnimationMode.EASE_OUT_SINE, timeline, "opactity", 255);
+    }
+   
+    public void slide_in (int direction, int sign, Clutter.Timeline timeline)
+    {
+        /* Place offscreen */
+        x = -sign * direction * width;
+        y = -sign * (1 - direction) * height;
+
+        /* Slide onscreen */
+        animate_with_timeline (Clutter.AnimationMode.EASE_OUT_BOUNCE, timeline, "x", 0.0, "y", 0.0);
+    }
+
+    public void slide_out (int direction, int sign, Clutter.Timeline timeline)
+    {
+        /* Slide offscreen */
+        animate_with_timeline (Clutter.AnimationMode.EASE_OUT_BOUNCE, timeline,
+                               "x", sign * direction * width,
+                               "y", sign * (1 - direction) * height);
+    }
+   
+    public void swap_in (float direction, Clutter.Timeline timeline)
+    {
+        /* Bring into foreground and make visible */
+        animate_with_timeline (Clutter.AnimationMode.EASE_IN_SINE, timeline,
+                               "opacity", 255,
+                               "depth", 0.0);
+    }
+
+    public void swap_out (float direction, Clutter.Timeline timeline)
+    {
+        /* Fade into background or drop down */
+        animate_with_timeline (Clutter.AnimationMode.EASE_IN_SINE, timeline,
+                               "depth", 250.0 * direction,
+                               "opacity", 0);
+    }
+
+    private void find_light (Light light, out int x, out int y)
+    {
+        x = y = 0;
+        for (x = 0; x < size; x++) 
+            for (y = 0; y < size; y++)
+                if (lights[x, y] == light)
+                    return;
+    }
+
+    private bool light_button_press_cb (Clutter.Actor actor, Clutter.ButtonEvent event)
+    {
+        int x, y;
+        find_light ((Light) actor, out x, out y);
+        toggle_light (x, y);
+        return false;
+    }
+
+    // Toggle a light and those in each cardinal direction around it.
+    public void toggle_light (int x, int y, bool animate = true)
+    {
+        if (!playable)
+            return;
+            
+        Clutter.Timeline? timeline = null;
+        if (animate)
+        {
+            timeline = new Clutter.Timeline (300);
+            timeline.completed.connect (toggle_completed_cb);
+        }
+
+        if ((int) x + 1 < size)
+            lights[(int) x + 1, (int) y].toggle (timeline);
+        if ((int) x - 1 >= 0)
+            lights[(int) x - 1, (int) y].toggle (timeline);
+        if ((int) y + 1 < size)
+            lights[(int) x, (int) y + 1].toggle (timeline);
+        if ((int) y - 1 >= 0)
+            lights[(int) x, (int) y - 1].toggle (timeline);
+
+        lights[(int) x, (int) y].toggle (timeline);
+
+        if (animate)
+            timeline.start ();
+    }
+
+    private void toggle_completed_cb ()
+    {
+        var cleared = true;
+        for (var x = 0; x < size; x++)
+            for (var y = 0; y < size; y++)
+                if (lights[x, y].is_lit)
+                    cleared = false;
+
+        if (cleared)
+            game_won ();
+    }
+        
+    // Pseudorandomly generates and sets the state of each light based on
+    // a level number; hopefully this is stable between machines, but that
+    // depends on GLib's PRNG stability. Also, provides some semblance of 
+    // symmetry for some levels.
+    public void load_level (int level)
+    {
+        /* We *must* not have level < 1, as the following assumes a nonzero, nonnegative number */
+        if (level < 1)
+            level = 1;
+
+        /* Clear level */
+        for (var x = 0; x < size; x++)
+            for (var y = 0; y < size; y++)
+                lights[x, y].is_lit = false;
+
+        /* Use the same pseudo-random levels */
+        Random.set_seed (level);
+
+        /* Levels require more and more clicks to make */
+        var solution_length = (int) Math.floor (2 * Math.log (level) + 1);
+
+        /* Do the moves the player needs to */
+        var sol = puzzle_generator.minimal_solution (solution_length);
+        for (var x = 0; x < size; x++)
+            for (var y = 0; y < size; y++)
+                if (sol[x, y])
+                    toggle_light (x, y, false);
+    }
+}
diff --git a/lightsoff/src/config.vapi b/lightsoff/src/config.vapi
new file mode 100644
index 0000000..6477226
--- /dev/null
+++ b/lightsoff/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/lightsoff/src/fixes.vapi b/lightsoff/src/fixes.vapi
new file mode 100644
index 0000000..7f05acb
--- /dev/null
+++ b/lightsoff/src/fixes.vapi
@@ -0,0 +1,9 @@
+namespace Clutter
+{
+    public const int KEY_Up;
+    public const int KEY_Down;
+    public const int KEY_Left;
+    public const int KEY_Right;
+    public const int KEY_Return;
+    public const int KEY_Escape;
+}
diff --git a/lightsoff/src/game-view.vala b/lightsoff/src/game-view.vala
new file mode 100644
index 0000000..fce0ee4
--- /dev/null
+++ b/lightsoff/src/game-view.vala
@@ -0,0 +1,276 @@
+public class GameView : Clutter.Group
+{
+    private Clutter.Texture backing_texture;
+    private Clutter.Texture highlight_texture;
+    private Clutter.Texture off_texture;
+    private Clutter.Texture on_texture;
+    private Clutter.Texture led_back_texture;
+    private Clutter.Texture arrow_texture;
+
+    private int current_level;
+
+    private List<Clutter.Actor> actor_remove_queue = null;
+
+    private LEDArray score_view;
+    private BoardView board_view;
+    private BoardView? new_board_view = null;
+    private Clutter.Actor backing_view;
+    private Clutter.Actor left_arrow;
+    private Clutter.Actor right_arrow;
+    private Clutter.Actor key_cursor_view;
+
+    private Clutter.Timeline timeline;
+    private int key_cursor_x = 0;
+    private int key_cursor_y = 0;
+    private bool key_cursor_ready = false;
+
+    private int last_direction = 0;
+
+    private int last_sign = 0;
+
+    public signal void level_changed (int level);
+
+    public GameView (int level)
+    {
+        try
+        {
+            backing_texture = new Clutter.Texture.from_file ("data/backing.svg");
+            highlight_texture = new Clutter.Texture.from_file ("data/highlight.svg");
+            off_texture = new Clutter.Texture.from_file ("data/off.svg");
+            on_texture = new Clutter.Texture.from_file ("data/on.svg");
+            led_back_texture = new Clutter.Texture.from_file ("data/led-back.svg");
+            arrow_texture = new Clutter.Texture.from_file ("data/arrow.svg");
+        }
+        catch (Clutter.TextureError e)
+        {
+            warning ("Failed to load textures: %s", e.message);
+        }
+
+        /* Add textures onto the scene so they can be cloned */
+        backing_texture.hide ();
+        add_actor (backing_texture);
+        highlight_texture.hide ();
+        add_actor (highlight_texture);
+        off_texture.hide ();
+        add_actor (off_texture);
+        on_texture.hide ();
+        add_actor (on_texture);
+        led_back_texture.hide ();
+        add_actor (led_back_texture);
+        arrow_texture.hide ();
+        add_actor (arrow_texture);
+
+        var real_board_width = 5 * off_texture.width + 4;
+        var real_board_height = 5 * off_texture.height + 4;
+
+        current_level = level;
+        board_view = create_board_view (current_level);
+        board_view.playable = true;
+        add_actor (board_view);
+
+        backing_view = new Clutter.Clone (backing_texture);
+        backing_view.set_position (0, real_board_height);
+        add_actor (backing_view);
+
+        score_view = new LEDArray (5, led_back_texture);
+        score_view.value = current_level;
+        score_view.set_anchor_point (score_view.width / 2, 0);
+        score_view.set_position (real_board_width / 2, real_board_height + 18);
+        add_actor (score_view);
+
+        set_size (real_board_width, score_view.y + score_view.height);
+
+        left_arrow = new Clutter.Clone (arrow_texture);
+        left_arrow.anchor_gravity = Clutter.Gravity.CENTER;
+        left_arrow.reactive = true;
+        left_arrow.button_release_event.connect (left_arrow_button_release_cb);
+        left_arrow.set_position ((score_view.x - score_view.anchor_x) / 2, score_view.y + (score_view.height / 2) - 10);
+        add_actor (left_arrow);
+
+        right_arrow = new Clutter.Clone (arrow_texture);
+        right_arrow.anchor_gravity = Clutter.Gravity.CENTER;
+        right_arrow.reactive = true;
+        right_arrow.button_release_event.connect (right_arrow_button_release_cb);
+        right_arrow.rotation_angle_y = 180;
+        right_arrow.set_position (real_board_width - left_arrow.x, score_view.y + (score_view.height / 2) - 10);
+        add_actor (right_arrow);
+
+        key_cursor_view = new Clutter.Clone (highlight_texture);
+        key_cursor_view.set_position (-100, -100);
+        key_cursor_view.anchor_gravity = Clutter.Gravity.CENTER;
+        add_actor (key_cursor_view);
+    }
+
+    private BoardView create_board_view (int level)
+    {
+        var view = new BoardView (off_texture, on_texture);
+        view.load_level (level);
+        view.game_won.connect (game_won_cb);
+        view.playable = false;
+
+        return view;
+    }
+
+    // The boards have finished transitioning; delete the old one!
+    private void transition_complete_cb ()
+    {
+        remove_actor (board_view);
+        board_view = new_board_view;
+        board_view.playable = true;
+        key_cursor_view.raise_top ();
+        new_board_view = null;
+        timeline = null;
+
+        // Remove all of the queued-for-removal actors
+        foreach (var actor in actor_remove_queue)
+        {
+            if (actor.get_parent () == null)
+                continue;
+            var group = (Clutter.Group) actor.get_parent ();
+            group.remove_actor (actor);
+        }
+        actor_remove_queue = null;
+    }
+
+    // The player won the game; create a new board, update the level count,
+    // and transition between the two boards in a random direction.
+    private void game_won_cb ()
+    {
+        if (timeline != null && timeline.is_playing ())
+            return;
+
+        current_level++;
+        score_view.value = current_level;
+
+        // Make sure the board transition is different than the previous.
+        var direction = 0;
+        var sign = 0;
+        do
+        {
+            direction = Random.int_range (0, 2); // x or y
+            sign = Random.boolean () ? 1 : -1; // left/right up/down
+        }
+        while (last_direction == direction || last_sign == sign);
+        last_direction = direction;
+        last_sign = sign;
+
+        new_board_view = create_board_view (current_level);
+        add_actor (new_board_view);
+        new_board_view.lower (board_view);
+
+        timeline = new Clutter.Timeline (1500);
+        new_board_view.slide_in (direction, sign, timeline);
+        board_view.slide_out (direction, sign, timeline);
+        timeline.completed.connect (transition_complete_cb);
+
+        level_changed (current_level);
+    }
+
+    private bool left_arrow_button_release_cb (Clutter.Actor actor, Clutter.ButtonEvent event)
+    {
+        swap_board (-1);
+        return false;
+    }
+
+    private bool right_arrow_button_release_cb (Clutter.Actor actor, Clutter.ButtonEvent event)
+    {
+        swap_board (1);
+        return false;
+    }
+
+    // The player asked to swap to a different level without completing
+    // the one in progress; this can occur either by clicking an arrow
+    // or by requesting a new game from the menu. Animate the new board
+    // in, depthwise, in the direction indicated by 'context'.
+    private void swap_board (int direction)
+    {
+        if (timeline != null && timeline.is_playing ())
+            return;
+
+        current_level += direction;
+        if (current_level <= 0)
+        {
+            current_level = 1;
+            return;
+        }
+
+        score_view.value = current_level;
+
+        timeline = new Clutter.Timeline (500);
+
+        new_board_view = create_board_view (current_level);
+        add_actor (new_board_view);
+        new_board_view.lower (board_view);
+        new_board_view.depth = -250 * direction;
+        new_board_view.opacity = 0;
+
+        new_board_view.swap_in (direction, timeline);
+        board_view.swap_out (direction, timeline);
+        timeline.completed.connect (transition_complete_cb);
+
+        level_changed (current_level);
+    }
+
+    public void hide_cursor ()
+    {
+        key_cursor_view.raise_top ();
+        key_cursor_view.animate (Clutter.AnimationMode.EASE_OUT_SINE, 250, "opacity", 0);
+        key_cursor_ready = false;
+    }
+
+    public void move_cursor (int x_step, int y_step)
+    {
+        if (key_cursor_ready)
+        {
+            key_cursor_x += x_step;
+            key_cursor_y += y_step;
+            key_cursor_x = int.max (key_cursor_x, 0);
+            key_cursor_x = int.min (key_cursor_x, 4); // FIXME: Get the size from the model
+            key_cursor_y = int.max (key_cursor_y, 0);
+            key_cursor_y = int.min (key_cursor_y, 4);
+        }
+
+        float x, y;
+        board_view.get_light_position (key_cursor_x, key_cursor_y, out x, out y);
+
+        if (key_cursor_ready)
+            key_cursor_view.animate (Clutter.AnimationMode.EASE_OUT_SINE, 250, "x", x, "y", y);
+        else
+        {
+            key_cursor_view.opacity = 0;
+            key_cursor_view.set_position (x, y);
+            key_cursor_view.animate (Clutter.AnimationMode.EASE_OUT_SINE, 250, "opacity", 255);
+        }
+
+        key_cursor_ready = true;
+    }
+    
+    public void activate_cursor ()
+    {
+        if (key_cursor_ready)
+            board_view.toggle_light (key_cursor_x, key_cursor_y);
+    }
+
+    public void reset_game ()
+    {
+        if (timeline != null && timeline.is_playing ())
+            return;
+
+        current_level = 1;
+        score_view.value = current_level;
+
+        timeline = new Clutter.Timeline (500);
+
+        new_board_view = create_board_view (current_level);
+        add_actor (new_board_view);
+        new_board_view.lower (board_view);
+        new_board_view.depth = 250;
+        new_board_view.opacity = 0;
+
+        new_board_view.swap_in (-1, timeline);
+        board_view.swap_out (-1, timeline);
+        timeline.completed.connect (transition_complete_cb);
+
+        level_changed (current_level);
+    }
+}
diff --git a/lightsoff/src/led-array.vala b/lightsoff/src/led-array.vala
new file mode 100644
index 0000000..72e7951
--- /dev/null
+++ b/lightsoff/src/led-array.vala
@@ -0,0 +1,133 @@
+public class LEDDigit : Clutter.CairoTexture
+{
+    private const int scale = 23;
+
+    private int _value = 0;
+    public int value
+    {
+        get { return _value; }
+        set { _value = value; invalidate (); }
+    }
+
+    private const bool segment_states[] =
+    {
+         true,  true,  true,  true,  true, false,  true,
+        false, false, false, false,  true, false,  true,
+         true, false,  true,  true, false,  true,  true,
+         true, false, false,  true,  true,  true,  true,
+        false,  true, false, false,  true,  true,  true,
+         true,  true, false,  true,  true,  true, false,
+         true,  true,  true,  true,  true,  true, false,
+         true, false, false, false,  true, false,  true,
+         true,  true,  true,  true,  true,  true,  true,
+         true,  true, false,  true,  true,  true,  true
+    };
+
+    public LEDDigit ()
+    {
+        set_surface_size (37, 65);
+        invalidate ();
+    }
+
+    public override bool draw (Cairo.Context cr)
+    {
+        cr.set_operator (Cairo.Operator.CLEAR);
+        cr.paint ();
+        cr.set_operator (Cairo.Operator.OVER);
+
+        var thickness = scale / 3;
+        var pointy = thickness / 2;
+        var margin = Math.floor (Math.log (scale));
+        var side = pointy + margin;
+
+        var offset = value * 7;
+        draw_segment (cr, side,               0,                         false, segment_states[offset]);
+        draw_segment (cr, 0,                  side,                      true,  segment_states[offset + 1]);
+        draw_segment (cr, 0,                  side + scale + margin * 2, true,  segment_states[offset + 2]);
+        draw_segment (cr, side,               2 * scale + 4 * margin,    false, segment_states[offset + 3]);
+        draw_segment (cr, scale + 2 * margin, side + scale + margin * 2, true,  segment_states[offset + 4]);
+        draw_segment (cr, side,               scale + 2 * margin,        false, segment_states[offset + 5]);
+        draw_segment (cr, scale + 2 * margin, side,                      true,  segment_states[offset + 6]);
+
+        return false;
+    }
+
+    private void draw_segment (Cairo.Context cr, double x, double y, bool is_vertical, bool is_lit)
+    {
+        if (is_lit)
+            cr.set_source_rgba (0.145, 0.541, 1, 1);
+        else
+            cr.set_source_rgba (0.2, 0.2, 0.2, 1);
+
+        cr.new_path ();
+
+        var thickness = scale / 3;
+        var pointy = thickness / 2;
+
+        if (is_vertical)
+        {
+            cr.move_to (x + thickness / 2, y + 0);
+            cr.line_to (x + 0, y + pointy);
+            cr.line_to (x + 0, y + scale - pointy);
+            cr.line_to (x + thickness / 2, y + scale);
+            cr.line_to (x + thickness, y + scale - pointy);
+            cr.line_to (x + thickness, y + pointy);
+        }
+        else
+        {
+            cr.move_to (x + 0, y + thickness / 2);
+            cr.line_to (x + pointy, y + 0);
+            cr.line_to (x + scale - pointy, y + 0);
+            cr.line_to (x + scale, y + thickness / 2);
+            cr.line_to (x + scale - pointy, y + thickness);
+            cr.line_to (x + pointy, y + thickness);
+        }
+
+        cr.close_path ();
+        cr.fill ();
+    }
+}
+
+public class LEDArray : Clutter.Group
+{
+    private List<LEDDigit> digits = null;
+    private Clutter.Actor back;
+
+    private int _value = 0;
+    public int value
+    {
+        get { return _value; }
+        set
+        {
+            _value = value;
+            var d_val = value;
+            foreach (var d in digits)
+            {
+                d.value = (int) Math.floor (d_val % 10);
+                d_val /= 10;
+            }
+        }
+    }
+
+    public LEDArray (int n_digits, Clutter.Actor back_texture)
+    {
+        var margin = 4;
+        var inner_x_margin = 10;
+        var inner_y_margin = -1;
+
+        back = new Clutter.Clone (back_texture);
+        add_actor (back);
+
+        for (var i = 0; i < n_digits; i++)
+        {
+            var d = new LEDDigit ();
+            d.set_anchor_point (0, d.height / 2);
+            d.x = i * (d.width + margin) + inner_x_margin;
+            d.y = back.height / 2 + inner_y_margin;
+            add_actor (d);
+            digits.prepend (d);
+        }
+
+        back.lower_bottom ();
+    }
+}
diff --git a/lightsoff/src/lightsoff.vala b/lightsoff/src/lightsoff.vala
new file mode 100644
index 0000000..a75c81b
--- /dev/null
+++ b/lightsoff/src/lightsoff.vala
@@ -0,0 +1,166 @@
+public class LightsOff
+{
+    private Settings settings;
+    private Gtk.Builder ui;
+    private Gtk.Window window;
+    private GameView game_view;
+    
+    private LightsOff () throws Error
+    {
+        settings = new Settings ("org.gnome.lightsoff");
+
+        ui = new Gtk.Builder();
+        ui.add_from_file ("data/lightsoff.ui");
+        ui.connect_signals (this);
+
+        window = (Gtk.Window) ui.get_object ("game_window");
+        window.hide.connect (Gtk.main_quit);
+
+        var box = (Gtk.Box) ui.get_object ("game_vbox");
+
+        var clutter_embed = new GtkClutter.Embed ();
+        clutter_embed.show ();
+        box.pack_start (clutter_embed, true, true);
+
+        var stage = (Clutter.Stage) clutter_embed.get_stage ();
+        stage.key_release_event.connect (key_release_event_cb);
+        stage.color = Clutter.Color.from_string ("#000000");
+        stage.use_fog = false;
+
+        game_view = new GameView (settings.get_int ("level"));
+        game_view.level_changed.connect (level_changed_cb);
+        game_view.show ();
+        stage.add_actor (game_view);
+
+        stage.set_size (game_view.width, game_view.height);
+        clutter_embed.set_size_request ((int) stage.width, (int) stage.height);        
+    }
+    
+    private void level_changed_cb (int level)
+    {
+        settings.set_int ("level", level);
+    }
+
+    private bool key_release_event_cb (Clutter.Actor actor, Clutter.KeyEvent event)
+    {
+        switch (event.keyval)
+        {
+        case Clutter.KEY_Escape:
+            game_view.hide_cursor ();
+            return true;
+        case Clutter.KEY_Down:
+            game_view.move_cursor (0, 1);
+            return true;
+        case Clutter.KEY_Up:
+            game_view.move_cursor (0, -1);
+            return true;
+        case Clutter.KEY_Left:
+            game_view.move_cursor (-1, 0);
+            return true;
+        case Clutter.KEY_Right:
+            game_view.move_cursor (1, 0);
+            return true;
+        case Clutter.KEY_Return:
+            game_view.activate_cursor ();
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    public void show ()
+    {
+        window.show ();
+    }
+
+    [CCode (cname = "G_MODULE_EXPORT new_game_cb", instance_pos = -1)]
+    public void new_game_cb (Gtk.Widget widget)
+    {
+        game_view.reset_game();
+    }
+
+    [CCode (cname = "G_MODULE_EXPORT quit_cb", instance_pos = -1)]
+    public void quit_cb (Gtk.Widget widget)
+    {
+        Gtk.main_quit ();
+    }
+
+    [CCode (cname = "G_MODULE_EXPORT help_cb", instance_pos = -1)]
+    public void help_cb (Gtk.Widget widget)
+    {
+        GnomeGamesSupport.help_display (window, "lightsoff", null);
+    }
+
+    [CCode (cname = "G_MODULE_EXPORT about_cb", instance_pos = -1)]
+    public void about_cb (Gtk.Widget widget)
+    {
+        string[] authors =
+        {
+            "Tim Horton",
+            "Robert Ancell",
+            null
+        };
+
+        string[] artists =
+        {
+            "Tim Horton",
+            "Ulisse Perusin",
+            null
+        };
+
+        string[] documenters =
+        {
+            "Eric Baudais",
+            null
+        };
+
+        Gtk.show_about_dialog (window,
+                               "program-name", _("Lights Off"),
+                               "version", VERSION,
+                               "comments",
+                               _("Turn off all the lights\n\nLights Off is a part of GNOME Games."),
+                               "copyright", "Copyright \xa9 2009 Tim Horton",
+                               "license", GnomeGamesSupport.get_license (_("Lights Off")),
+                               "wrap-license", true,
+                               "authors", authors,
+                               "artists", artists,
+                               "documenters", documenters,
+                               "translator-credits", _("translator-credits"),
+                               "logo-icon-name", "gnome-lightsoff",
+                               "website", "http://www.gnome.org/projects/gnome-games";,
+                               "website-label", _("GNOME Games web site"),
+                               null);
+    }
+
+    public static int main (string[] args)
+    {
+        Environment.set_prgname ("lightsoff");
+
+        if (GtkClutter.init (ref args) != Clutter.InitError.SUCCESS)
+        {
+            warning ("Failed to initialise Clutter");
+            return Posix.EXIT_FAILURE;
+        }
+
+        GnomeGamesSupport.runtime_init ("lightsoff");
+        GnomeGamesSupport.stock_init ();
+
+        LightsOff app;
+        try
+        {
+            app = new LightsOff ();
+            app.show ();
+        }
+        catch (Error e)
+        {
+            warning ("Failed to create application: %s", e.message);
+            return Posix.EXIT_FAILURE;
+        }
+
+        Gtk.main ();
+
+        GnomeGamesSupport.runtime_shutdown();
+
+        return Posix.EXIT_SUCCESS;
+    }
+}
diff --git a/lightsoff/src/puzzle-generator.vala b/lightsoff/src/puzzle-generator.vala
new file mode 100644
index 0000000..6103d59
--- /dev/null
+++ b/lightsoff/src/puzzle-generator.vala
@@ -0,0 +1,198 @@
+// Puzzle generation logic:
+// We want to measure a puzzle's difficulty by the number of button presses
+// needed to solve it, and have that increase in a controlled manner.
+//
+// Lights Off can be seen as a linear algebra problem over the field GF(2).
+// (See e.g. the first page of "Turning Lights Out with Linear Algebra".)
+// The linear map from button-press strategies to light configurations
+// will in general have a nullspace (of dimension n).
+// Thus, any solvable puzzle has 2^n solutions, given by a fixed solution
+// plus an element of the nullspace.
+// A solution is thus optimal if it presses at most half the buttons
+// which make up any element of the nullspace.
+//
+// A basis for the nullspace splits the board into (up to) 2^n regions,
+// defined by which basic nullspace elements include a given button.
+// (This determines which nullspace elements include the same button.)
+// Thus, a solution is optimal if it includes at most half the buttons in any
+// region (except the region lying outside all null-sets).
+// The converse is not true, but (at least if the regions are even-sized)
+// does hold for a large number of puzzles up to the highest difficulty level.
+// (Certainly, on average, at most half the lights which belong to at least
+// one null set may be used.)
+
+public class PuzzleGenerator
+{
+    private int size;
+    private int max_solution_length;
+    private int[] region_of;
+    private int[] region_size;
+
+    public PuzzleGenerator (int size)
+    {
+        this.size = size;
+        var adj_matrix = new int[size * size, size * size];
+        for (var x0 = 0; x0 < size; x0++)
+        {
+            for (var y0 = 0; y0 < size; y0++)
+            {
+                for (var x1 = 0; x1 < size; x1++)
+                {
+                    for (var y1 = 0; y1 < size; y1++)
+                    {
+                        var dx = x0 - x1;
+                        var dy = y0 - y1;
+                        adj_matrix[x0 * size + y0, x1 * size + y1] = dx*dx + dy*dy <= 1 ? 1 : 0;
+                    }
+                }
+            }
+        }
+
+        // Row-reduction over field with two elements
+        List<int> non_pivot_cols = null;
+        var ipiv = 0;
+        for (var jpiv = 0; jpiv < size * size; jpiv++)
+        {
+            var is_pivot_col = false;
+            for (var i = ipiv; i < size * size; i++)
+            {
+                if (adj_matrix[i, jpiv] != 0)
+                {
+                    /* Swap rows */
+                    if (i != ipiv)
+                    {
+                        for (var z = 0; z < size * size; z++)
+                        {
+                            var t = adj_matrix[i, z];
+                            adj_matrix[i, z] = adj_matrix[ipiv, z];
+                            adj_matrix[ipiv, z] = t;
+                        }
+                    }
+
+                    for (var j = ipiv+1; j < size * size; j++)
+                    {
+                        if (adj_matrix[j, jpiv] != 0)
+                        {
+                            for (var k = 0; k < size * size; k++)
+                                adj_matrix[j, k] ^= adj_matrix[ipiv, k];
+                        }
+                    }
+                    is_pivot_col = true;
+                    ipiv++;
+                    break;
+                }
+            }
+
+            if (!is_pivot_col)
+                non_pivot_cols.append (jpiv);
+        }
+
+        // Use back-substitution to solve Adj*x = 0, once with each
+        // free variable set to 1 (and the others to 0).
+        var basis_for_ns = new int[non_pivot_cols.length (), size * size];
+        var n = 0;
+        foreach (var col in non_pivot_cols)        
+        {
+            for (var j = 0; j < size * size; j++)
+                basis_for_ns[n, j] = 0;
+            basis_for_ns[n, col] = 1;
+
+            for (var i = size * size - 1; i >= 0; i--)
+            {
+                var jpiv = 0;
+                for (; jpiv < size * size; jpiv++)
+                    if (adj_matrix[i, jpiv] != 0)
+                        break;
+                if (jpiv == size * size)
+                    continue;
+                for (var j = jpiv + 1; j < size * size; j++)
+                    basis_for_ns[n, jpiv] ^= adj_matrix[i, j] * basis_for_ns[n, j];
+            }
+
+            n++;
+        }
+
+        // A button's region # is a binary # with 1's in a place corresponding
+        // to any null-vector which contains it.
+        region_size = new int [1 << non_pivot_cols.length ()];
+        for (var j = 0; j < region_size.length; j++)
+            region_size[j] = 0;
+        region_of = new int[size * size];
+        for (var i = 0; i < size * size; i++)
+        {
+            region_of[i] = 0;
+            for (var j = 0; j < non_pivot_cols.length (); j++)
+            {
+                if (basis_for_ns[j, i] != 0)
+                    region_of[i] += 1 << j;
+            }
+            region_size[region_of[i]]++;
+        }
+
+        max_solution_length = region_size[0];
+        for (var j = 1; j < region_size.length; j++)
+            max_solution_length += (int) Math.floor (region_size[j] / 2);
+    }
+
+    public bool[,] minimal_solution (int solution_length)
+    {
+        var sol = new bool[size, size];
+        for (var x = 0; x < size; x++)
+            for (var y = 0; y < size; y++)
+                sol[x, y] = false;
+
+        var presses_in_region = new int[region_size.length];
+        for (var i = 0; i < region_size.length; i++)
+            presses_in_region[i] = 0;
+
+        /* Note this should be Random.int_range (0, 3) but it is like this to match the old behaviour */
+        var sym = (int) Math.floor (3 * Random.next_double ());
+
+        var presses_left = int.min (solution_length, max_solution_length);
+        while (presses_left > 0)
+        {
+            int x[2], y[2];
+
+            // Pick a spot (x[0], y[0]), a corner if one is needed
+            /* Note this should be Random.int_range (0, size) but it is like this to match the old behaviour */
+            x[0] = (int) Math.round ((size - 1) * Random.next_double ());
+            y[0] = (int) Math.round ((size - 1) * Random.next_double ());
+
+            // Also pick a symmetric spot, to take if possible
+            if (sym == 0)
+            {
+                x[1] = size - 1 - x[0];
+                y[1] = y[0];
+            }
+            else if (sym == 1)
+            {
+                x[1] = size - 1 - x[0];
+                y[1] = size - 1 - y[0];
+            }
+            else
+            {
+                x[1] = x[0];
+                y[1] = size - 1 - y[0];
+            }
+
+            // Make each move if it doesn't fill a region more than halfway.
+            for (var k = 0; k < 2; k++)
+            {
+                var r = region_of[x[k] * size + y[k]];
+                if (r == 0 || 2 * (presses_in_region[r] + 1) <= region_size[r])
+                {
+                    if (sol[x[k], y[k]])
+                        continue;
+                    sol[x[k], y[k]] = true;
+                    presses_in_region[r]++;
+                    presses_left--;
+                }
+                if (presses_left == 0)
+                    break;
+            }
+        }
+
+        return sol;
+    }
+}
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 365dc46..e401801 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -113,10 +113,13 @@ libgames-support/games-show.c
 libgames-support/games-sound.c
 libgames-support/games-stock.c
 [type: gettext/glade]lightsoff/data/lightsoff.ui
-[type: gettext/glade]lightsoff/data/settings.ui
-lightsoff/lightsoff.desktop.in.in
-lightsoff/lightsoff.schemas.in
-lightsoff/src/About.js
+lightsoff/data/lightsoff.desktop.in.in
+lightsoff/data/org.gnome.lightsoff.gschema.xml.in
+lightsoff/src/board-view.vala
+lightsoff/src/game-view.vala
+lightsoff/src/led-array.vala
+lightsoff/src/lightsoff.vala
+lightsoff/src/puzzle-generator.vala
 mahjongg/data/mahjongg.desktop.in.in
 mahjongg/data/org.gnome.mahjongg.gschema.xml.in
 mahjongg/data/translatable_game_names.h



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