[gnome-2048] Add undo support



commit 8fc2ec808e3d54d9fa544c9f213df748f50a28f4
Author: Juan R. GarcĂ­a Blanco <juanrgar gmail com>
Date:   Sun Mar 1 22:01:32 2015 +0100

    Add undo support
    
        * data/org.gnome.2048.gschema.xml: Add allow-undo and
        allow-undo-max keys.
        * data/preferences.ui: Add switch to control undo.
        * src/application.vala: Connect to ::undo_* signals to
        enable/disable undo action.
        * src/game.vala: Add ::undo_enabled and ::undo_disabled signals;
        create stack of undoable operations; push and pop operations
        appropriately
        * src/grid.vala: Add clone () method to effectively clone a Grid
        instance.
        * See https://bugzilla.gnome.org/show_bug.cgi?id=745332

 data/org.gnome.2048.gschema.xml |   10 ++++++
 data/preferences.ui             |   44 ++++++++++++++++++++------
 src/application.vala            |   19 +++++++++++
 src/game.vala                   |   65 ++++++++++++++++++++++++++++++++++----
 src/grid.vala                   |   10 ++++++
 5 files changed, 130 insertions(+), 18 deletions(-)
---
diff --git a/data/org.gnome.2048.gschema.xml b/data/org.gnome.2048.gschema.xml
index df0b3a6..4bf5241 100644
--- a/data/org.gnome.2048.gschema.xml
+++ b/data/org.gnome.2048.gschema.xml
@@ -40,5 +40,15 @@
       <summary>Animations speed</summary>
       <description>Duration of animations: show tile, move tile, and dim tile.</description>
     </key>
+    <key name="allow-undo" type="b">
+      <default>false</default>
+      <summary>Allow undo</summary>
+      <description>Whether tile movements can be undone.</description>
+    </key>
+    <key name="allow-undo-max" type="i">
+      <default>10</default>
+      <summary>Number of undo movements</summary>
+      <description>Maximum number of tile movements that can be undone.</description>
+    </key>
   </schema>
 </schemalist>
diff --git a/data/preferences.ui b/data/preferences.ui
index faadf4c..3c93ca0 100644
--- a/data/preferences.ui
+++ b/data/preferences.ui
@@ -74,6 +74,31 @@
               </packing>
             </child>
             <child>
+              <object class="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Animations speed</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScale" id="animsscale">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="adjustment">animationsspeed</property>
+                <property name="inverted">True</property>
+                <property name="round_digits">1</property>
+                <property name="draw_value">False</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
               <object class="GtkLabel" id="label2">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
@@ -82,7 +107,7 @@
               </object>
               <packing>
                 <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
+                <property name="top_attach">3</property>
               </packing>
             </child>
             <child>
@@ -93,32 +118,29 @@
               </object>
               <packing>
                 <property name="left_attach">1</property>
-                <property name="top_attach">2</property>
+                <property name="top_attach">3</property>
               </packing>
             </child>
             <child>
-              <object class="GtkLabel" id="label3">
+              <object class="GtkLabel" id="label4">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Animations speed</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Allow undo</property>
               </object>
               <packing>
                 <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
+                <property name="top_attach">2</property>
               </packing>
             </child>
             <child>
-              <object class="GtkScale" id="animsscale">
+              <object class="GtkSwitch" id="undoswitch">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
-                <property name="adjustment">animationsspeed</property>
-                <property name="inverted">True</property>
-                <property name="round_digits">1</property>
-                <property name="draw_value">False</property>
               </object>
               <packing>
                 <property name="left_attach">1</property>
-                <property name="top_attach">1</property>
+                <property name="top_attach">2</property>
               </packing>
             </child>
           </object>
diff --git a/src/application.vala b/src/application.vala
index 44ebcff..d4d0396 100644
--- a/src/application.vala
+++ b/src/application.vala
@@ -24,6 +24,7 @@ public class Application : Gtk.Application
 
   private Gtk.Window _window;
   private Gtk.HeaderBar _header_bar;
+  private Gtk.Button _undo_button;
   private Gtk.Button _new_game_button;
   private Gtk.AboutDialog _about_dialog;
   private Gtk.Dialog _preferences_dialog;
@@ -50,6 +51,7 @@ public class Application : Gtk.Application
   private const GLib.ActionEntry[] action_entries =
   {
     { "new-game",       new_game_cb       },
+    { "undo",           undo_cb           },
     { "scores",         scores_cb         },
     { "about",          about_cb          },
     { "preferences",    preferences_cb    },
@@ -160,6 +162,12 @@ public class Application : Gtk.Application
       }
       debug ("target value reached");
     });
+    _game.undo_enabled.connect ((s) => {
+      ((SimpleAction) lookup_action ("undo")).set_enabled (true);
+    });
+    _game.undo_disabled.connect ((s) => {
+      ((SimpleAction) lookup_action ("undo")).set_enabled (false);
+    });
   }
 
   private void _create_window (Gtk.Builder builder)
@@ -203,6 +211,11 @@ public class Application : Gtk.Application
     _score = new Gtk.Label ("0");
     _header_bar.pack_end (_score);
 
+    _undo_button = new Gtk.Button.from_icon_name ("edit-undo-symbolic");
+    _undo_button.set_action_name ("app.undo");
+    _header_bar.pack_start (_undo_button);
+    ((SimpleAction) lookup_action ("undo")).set_enabled (false);
+
     _new_game_button = new Gtk.Button.with_label (_("New Game"));
     _new_game_button.set_action_name ("app.new-game");
     _header_bar.pack_start (_new_game_button);
@@ -281,6 +294,7 @@ public class Application : Gtk.Application
 
     _settings.bind ("do-congrat", builder.get_object ("congratswitch"), "active", 
GLib.SettingsBindFlags.DEFAULT);
     _settings.bind ("animations-speed", builder.get_object ("animationsspeed"), "value", 
GLib.SettingsBindFlags.DEFAULT);
+    _settings.bind ("allow-undo", builder.get_object ("undoswitch"), "active", 
GLib.SettingsBindFlags.DEFAULT);
   }
 
   private void _create_congrats_dialog (Gtk.Builder builder)
@@ -324,6 +338,11 @@ public class Application : Gtk.Application
     _game.new_game ();
   }
 
+  private void undo_cb ()
+  {
+    _game.undo ();
+  }
+
   private void scores_cb ()
   {
     _scores_ctx.run_dialog ();
diff --git a/src/game.vala b/src/game.vala
index dfd37af..b20ad67 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -49,6 +49,10 @@ public class Game : GLib.Object
   private Clutter.TransitionGroup _move_trans;
   private int _animations_duration;
 
+  private bool _allow_undo;
+  private uint _undo_stack_max_size;
+  private Gee.LinkedList<Grid> _undo_stack;
+
   private GLib.Settings _settings;
 
   private string _saved_path;
@@ -57,6 +61,8 @@ public class Game : GLib.Object
 
   public signal void finished ();
   public signal void target_value_reached (uint val);
+  public signal void undo_enabled ();
+  public signal void undo_disabled ();
 
   public Game (GLib.Settings settings)
   {
@@ -76,6 +82,10 @@ public class Game : GLib.Object
     _to_hide = new Gee.LinkedList<TileMovement?> ();
     _to_show = new Gee.LinkedList<Tile?> ();
 
+    _undo_stack = new Gee.LinkedList<Grid> ();
+    _allow_undo = _settings.get_boolean ("allow-undo");
+    _undo_stack_max_size = _settings.get_int ("allow-undo-max");
+
     _saved_path = Path.build_filename (Environment.get_user_data_dir (), "gnome-2048", "saved");
 
     _state = GameState.STOPPED;
@@ -96,12 +106,24 @@ public class Game : GLib.Object
   public void new_game ()
   {
     _grid.clear ();
+    _undo_stack.clear ();
     _clear_foreground ();
     score = 0;
     _state = GameState.SHOWING_FIRST_TILE;
     _create_random_tile ();
   }
 
+  public void undo ()
+  {
+    Grid grid = _undo_stack.poll_head ();
+    _clear_foreground ();
+    _grid = grid;
+    _restore_foreground (false);
+
+    if (_undo_stack.size == 0)
+      undo_disabled ();
+  }
+
   public void save_game ()
   {
     string contents = "";
@@ -139,7 +161,7 @@ public class Game : GLib.Object
     if (_background != null)
       _clear_background ();
     _init_background ();
-    _restore_foreground ();
+    _restore_foreground (true);
 
     debug ("game restored successfully");
     return true;
@@ -169,9 +191,18 @@ public class Game : GLib.Object
   public bool reload_settings ()
   {
     int rows, cols;
+    bool allow_undo;
 
     _animations_duration = (int)_settings.get_double ("animations-speed");
 
+    allow_undo = _settings.get_boolean ("allow-undo");
+    if (_allow_undo && !allow_undo) {
+      _undo_stack.clear ();
+      undo_disabled ();
+    }
+    _allow_undo = allow_undo;
+    _undo_stack_max_size = _settings.get_int ("allow-undo-max");
+
     rows = _settings.get_int ("rows");
     cols = _settings.get_int ("cols");
 
@@ -299,7 +330,7 @@ public class Game : GLib.Object
     Tile tile;
 
     if (_grid.new_tile (out tile)) {
-      _create_show_hide_transition ();
+      _create_show_hide_transition (true);
 
       _create_tile (tile);
       _to_show.add (tile);
@@ -336,6 +367,8 @@ public class Game : GLib.Object
 
     bool has_moved;
 
+    _store_movement ();
+
     _move_trans = new Clutter.TransitionGroup ();
     _move_trans.stopped.connect (_on_move_trans_stopped);
     _move_trans.set_duration (_animations_duration);
@@ -362,6 +395,8 @@ public class Game : GLib.Object
 
     bool has_moved;
 
+    _store_movement ();
+
     _move_trans = new Clutter.TransitionGroup ();
     _move_trans.stopped.connect (_on_move_trans_stopped);
     _move_trans.set_duration (_animations_duration);
@@ -388,6 +423,8 @@ public class Game : GLib.Object
 
     bool has_moved;
 
+    _store_movement ();
+
     _move_trans = new Clutter.TransitionGroup ();
     _move_trans.stopped.connect (_on_move_trans_stopped);
     _move_trans.set_duration (_animations_duration);
@@ -414,6 +451,8 @@ public class Game : GLib.Object
 
     bool has_moved;
 
+    _store_movement ();
+
     _move_trans = new Clutter.TransitionGroup ();
     _move_trans.stopped.connect (_on_move_trans_stopped);
     _move_trans.set_duration (_animations_duration);
@@ -553,7 +592,7 @@ public class Game : GLib.Object
     }
   }
 
-  private void _restore_foreground ()
+  private void _restore_foreground (bool animate)
   {
     uint val;
     GridPosition pos;
@@ -561,7 +600,7 @@ public class Game : GLib.Object
     int rows = _grid.rows;
     int cols = _grid.cols;
 
-    _create_show_hide_transition ();
+    _create_show_hide_transition (animate);
 
     for (int i = 0; i < rows; i++) {
       for (int j = 0; j < cols; j++) {
@@ -591,7 +630,7 @@ public class Game : GLib.Object
 
     _move_trans.remove_all ();
 
-    _create_show_hide_transition ();
+    _create_show_hide_transition (true);
 
     foreach (var e in _to_hide) {
       _dim_tile (e.from);
@@ -636,11 +675,11 @@ public class Game : GLib.Object
     GLib.Timeout.add (100, _finish_move);
   }
 
-  private void _create_show_hide_transition ()
+  private void _create_show_hide_transition (bool animate)
   {
     _show_hide_trans = new Clutter.TransitionGroup ();
     _show_hide_trans.stopped.connect (_on_show_hide_trans_stopped);
-    _show_hide_trans.set_duration (_animations_duration);
+    _show_hide_trans.set_duration (animate ? _animations_duration : 10);
   }
 
   private bool _finish_move ()
@@ -680,4 +719,16 @@ public class Game : GLib.Object
 
     return false;
   }
+
+  private void _store_movement ()
+  {
+    if (_allow_undo) {
+      if (_undo_stack.size == _undo_stack_max_size)
+        _undo_stack.poll_tail ();
+      _undo_stack.offer_head (_grid.clone ());
+      if (_undo_stack.size == 1) {
+        undo_enabled ();
+      }
+    }
+  }
 }
diff --git a/src/grid.vala b/src/grid.vala
index d402b65..ef2a16d 100644
--- a/src/grid.vala
+++ b/src/grid.vala
@@ -45,6 +45,16 @@ public class Grid : GLib.Object
     get; set;
   }
 
+  public Grid clone ()
+  {
+    Grid grid = new Grid (_rows, _cols);
+    grid._grid = _grid;
+    grid._target_value = _target_value;
+    grid._target_value_reached = _target_value_reached;
+
+    return grid;
+  }
+
   public void clear ()
   {
     for (uint i = 0; i < _grid.length[0]; i++) {


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