[gnome-sudoku/vala-port] Implemented save game feature



commit 7dcf1da8bd8b9819d8b1f6ecf8d37b6297065cc4
Author: Parin Porecha <parinporecha gmail com>
Date:   Fri May 9 15:08:19 2014 +0530

    Implemented save game feature
    
    Created SudokuSaver class in sudoku-saver.vala
    
    https://bugzilla.gnome.org/show_bug.cgi?id=633464

 TODO                  |   12 +--
 configure.ac          |    1 +
 data/gnome-sudoku.ui  |   35 ++++++++++
 src/Makefile.am       |    4 +-
 src/gnome-sudoku.vala |   35 +++++++++-
 src/sudoku-board.vala |   18 +++++
 src/sudoku-game.vala  |    5 ++
 src/sudoku-saver.vala |  179 +++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 279 insertions(+), 10 deletions(-)
---
diff --git a/TODO b/TODO
index b3529a4..2188932 100644
--- a/TODO
+++ b/TODO
@@ -31,17 +31,13 @@ mcatanzaro: Too confusing in general:
    mouse over the text to see anything, and even then there's no explanation of
    what it is.
 
-
-No way to save a game
-
-No way to know if a game has already been played or not:
-  For this SudokuTracker class needs to be ported. Once it is complete, enable the corresponding options -
+enable the options -
   'MarkAsPlayedToggle' and 'includeOldGamesToggle' in data/print_games.ui (for multiple print dialog)
 
 Currently we only read sudokus from data files:
   We need to complete the port of SudokuGenerator class to be able to generate sudokus as desired.
 
-Port saver.py for saving games, fullscreen mode feature, highlighter feature
+Port fullscreen mode feature, highlighter feature
 
 mcatanzaro: We're removing Fullscreen from most other games because it usually
             looks pretty bad. Games maximized with the header bar usually fit
@@ -61,8 +57,6 @@ mcatanzaro: This number pad was from an earlier design; I think we can remove
 
 - The preview window does not resize to it's original size (the size at startup) after 'New Game' is clicked.
 
-- No way to save a game (port saver.py)
-
 - Sudoku master has total 19 menu options (8 in 'Game', 5 in 'Settings', 4 in 'Tools', 2 in 'Help')
   Current vala-port design and mockups have only one panel menu and it cannot accomodate all the 19 options
   Ask aday about this.
@@ -78,3 +72,5 @@ mcatanzaro: This number pad was from an earlier design; I think we can remove
   Instead, they are displayed at random positions in the cells (looks random, maybe there is a bug in 
display offsets)
 
 - Print (single sudoku) should be disabled when not in-game
+
+- Message Dialogs haven't been ported. Port gtk_goodies/dialog_extras.py
diff --git a/configure.ac b/configure.ac
index 81358cd..5e66425 100644
--- a/configure.ac
+++ b/configure.ac
@@ -21,6 +21,7 @@ PKG_CHECK_MODULES(GNOME_SUDOKU, [
   gio-2.0
   gtk+-3.0 >= $GTK_REQUIRED
   gee-0.8
+  json-glib-1.0
 ])
 
 AC_PATH_PROG([APPDATA_VALIDATE], [appdata-validate], [/bin/true])
diff --git a/data/gnome-sudoku.ui b/data/gnome-sudoku.ui
index ba1bc34..55ca191 100644
--- a/data/gnome-sudoku.ui
+++ b/data/gnome-sudoku.ui
@@ -153,6 +153,41 @@
                                 <property name="position">2</property>
                             </packing>
                         </child>
+                        <child>
+                            <object class="GtkBox" id="savegame_box">
+                                <property name="visible">True</property>
+                                <child>
+                                    <object class="GtkBox" id="savegame_grid">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                        <child>
+                                            <object class="GtkLabel" id="savegame_label">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="label" translatable="yes">Latest 
Saved</property>
+                                                <attributes>
+                                                    <attribute name="weight" value="bold"/>
+                                                </attributes>
+                                            </object>
+                                        </child>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">20</property>
+                                    </packing>
+                                </child>
+                            </object> <!-- End of save game box -->
+                            <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">3</property>
+                                <property name="padding">20</property>
+                            </packing>
+                        </child>
                     </object> <!-- End of new start_box -->
                 </child>
                 <child>
diff --git a/src/Makefile.am b/src/Makefile.am
index a010fdd..f84ed60 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,6 +15,7 @@ gnome_sudoku_SOURCES = \
        sudoku-view.vala \
        number-picker.vala \
        sudoku-printer.vala \
+       sudoku-saver.vala \
        $(BUILT_SOURCES)
 
 gnome_sudoku_CFLAGS = \
@@ -32,7 +33,8 @@ gnome_sudoku_VALAFLAGS = \
        --pkg posix \
        --pkg gio-2.0 \
        --pkg gtk+-3.0 \
-       --pkg gee-0.8
+       --pkg gee-0.8 \
+       --pkg json-glib-1.0
 
 gnome-sudoku-resources.c: gnome-sudoku.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) 
--generate-dependencies gnome-sudoku.gresource.xml)
        $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --generate-source $<
diff --git a/src/gnome-sudoku.vala b/src/gnome-sudoku.vala
index 72da433..171b3a0 100644
--- a/src/gnome-sudoku.vala
+++ b/src/gnome-sudoku.vala
@@ -28,9 +28,12 @@ public class Sudoku : Gtk.Application
     private SudokuView medium_preview;
     private SudokuView hard_preview;
     private SudokuView very_hard_preview;
+    private SudokuView savegame_preview;
 
     private SudokuStore sudoku_store;
 
+    private SudokuSaver saver;
+
     // Help Stuff
     private Box naked_single_items;
     private Box hidden_single_items;
@@ -108,12 +111,14 @@ public class Sudoku : Gtk.Application
         });
 
         sudoku_store = new SudokuStore ();
+        saver = new SudokuSaver ();
         //SudokuGenerator gen = new SudokuGenerator();
 
         var easy_grid = (Box) builder.get_object ("easy_grid");
         var medium_grid = (Box) builder.get_object ("medium_grid");
         var hard_grid = (Box) builder.get_object ("hard_grid");
         var very_hard_grid = (Box) builder.get_object ("very_hard_grid");
+        var savegame_grid = (Box) builder.get_object ("savegame_grid");
 
         var easy_board = sudoku_store.get_random_easy_board ();
         //gen.make_symmetric_puzzle(Random.int_range(0, 4));
@@ -171,11 +176,35 @@ public class Sudoku : Gtk.Application
             return false;
         });
 
+        // FIXME: Remove the preview and directly start the game instead
+        var savegame = saver.get_savedgame ();
+        if (savegame == null)
+            savegame = new SudokuGame (sudoku_store.get_random_easy_board ());
+        //gen.make_symmetric_puzzle(Random.int_range(0, 4));
+        // gen.generate (DifficultyRating.medium_range);
+        savegame_preview = new SudokuView (savegame, true);
+        savegame_preview.show ();
+        savegame_grid.pack_start (savegame_preview);
+
+        savegame_grid.button_press_event.connect ((event) => {
+            if (event.button == 1)
+                start_game (savegame.board);
+
+            return false;
+        });
+
         show_start ();
 
         builder.connect_signals (this);
 
         window.show ();
+
+        window.delete_event.connect ((event) => {
+            if (game_box.visible)
+                saver.save_game (game);
+
+            return false;
+        });
     }
 
     private void start_game (SudokuBoard board)
@@ -234,7 +263,7 @@ public class Sudoku : Gtk.Application
         game.board.completed.connect (() => {
             view.dance ();
 
-            var time = game.timer.elapsed ();
+            var time = game.get_total_time_played ();
 
             for (var i = 0; i < game.board.rows; i++)
             {
@@ -244,6 +273,8 @@ public class Sudoku : Gtk.Application
                 }
             }
 
+            saver.add_game_to_finished (game);
+
             var dialog = new MessageDialog(null, DialogFlags.DESTROY_WITH_PARENT, MessageType.INFO, 
ButtonsType.NONE, "Well done, you completed the puzzle in %f seconds", time);
 
             dialog.add_button ("Same difficulty again", 0);
@@ -474,6 +505,8 @@ public class Sudoku : Gtk.Application
 
     public void quit_cb ()
     {
+        if (game_box.visible)
+            saver.save_game (game);
         window.destroy ();
     }
 
diff --git a/src/sudoku-board.vala b/src/sudoku-board.vala
index 0569cca..95f6ba1 100644
--- a/src/sudoku-board.vala
+++ b/src/sudoku-board.vala
@@ -12,6 +12,8 @@ public class SudokuBoard
     private bool[,] possible_in_col;            /* if specific value is possible in specific col */
     private bool[,,] possible_in_block;         /* if specific value is possible in specific block */
 
+    public double previous_played_time { set; get; default = 0; }
+
     /* Number of rows in one block */
     private int _block_rows;
     public int block_rows
@@ -471,6 +473,22 @@ public class SudokuBoard
         stdout.printf (" ]");
     }
 
+    public string to_string (bool get_original_state = false)
+    {
+        var board_string = "";
+        for (var i = 0; i < _rows; i++)
+        {
+            for (var j = 0; j < _cols; j++)
+            {
+                if (is_fixed[i, j])
+                    board_string += cells[i, j].to_string ();
+                else
+                    board_string += get_original_state ? "0" : cells[i, j].to_string ();
+            }
+        }
+        return board_string;
+    }
+
     public int[,] get_cells()
     {
         return cells;
diff --git a/src/sudoku-game.vala b/src/sudoku-game.vala
index 42c2473..005d1fa 100644
--- a/src/sudoku-game.vala
+++ b/src/sudoku-game.vala
@@ -155,4 +155,9 @@ public class SudokuGame
             add_to_stack (ref to, -1, -1, num);
         }
     }
+
+    public double get_total_time_played ()
+    {
+        return board.previous_played_time + timer.elapsed ();
+    }
 }
diff --git a/src/sudoku-saver.vala b/src/sudoku-saver.vala
new file mode 100644
index 0000000..19c662e
--- /dev/null
+++ b/src/sudoku-saver.vala
@@ -0,0 +1,179 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+using Gee;
+
+public class SudokuSaver
+{
+    private string savegame_file;
+    private string finishgame_dir;
+
+    public SudokuSaver() {
+        try {
+
+            var config_dir = Environment.get_user_data_dir ();
+            var sudoku_data_dir = Path.build_path (Path.DIR_SEPARATOR_S, config_dir, "gnome-sudoku");
+            savegame_file = Path.build_path (Path.DIR_SEPARATOR_S, sudoku_data_dir, "savefile");
+            finishgame_dir = Path.build_path (Path.DIR_SEPARATOR_S, sudoku_data_dir, "finished");
+
+            {
+                var file = File.new_for_path (sudoku_data_dir);
+                if (!file.query_exists ())
+                    file.make_directory ();
+
+                file = File.new_for_path (finishgame_dir);
+                if (!file.query_exists ())
+                    file.make_directory ();
+            }
+        }  catch (Error e) {
+            warning ("%s", e.message);
+        }
+    }
+
+    public SudokuGame? get_savedgame ()
+    {
+        var file = File.new_for_path (savegame_file);
+        if (!file.query_exists ())
+            return null;
+
+        return parse_json_to_game (savegame_file);
+    }
+
+    public void save_game (SudokuGame game)
+    {
+        create_file_for_game (game, savegame_file);
+    }
+
+    public void add_game_to_finished (SudokuGame game)
+    {
+        var file_name = game.board.to_string (true) + ".save";
+        var file_path = Path.build_path (Path.DIR_SEPARATOR_S, finishgame_dir, file_name);
+        create_file_for_game (game, file_path);
+
+        // Delete savegame file
+        var file = File.new_for_path (savegame_file);
+        if (file.query_exists ())
+            file.delete ();
+    }
+
+    private void create_file_for_game (SudokuGame game, string file_name)
+    {
+        var json_str = serialize_game_to_json (game);
+        var file = File.new_for_path (file_name);
+
+        try {
+            FileUtils.set_contents (file_name, json_str);
+        } catch (Error e) {
+            warning ("%s", e.message);
+        }
+    }
+
+    private string serialize_game_to_json (SudokuGame game)
+    {
+        var board = game.board;
+        var board_cells = board.get_cells ();
+        Json.Builder builder = new Json.Builder ();
+
+        builder.begin_object ();
+        builder.set_member_name ("difficulty_rating");
+        builder.add_double_value (board.difficulty_rating);
+        builder.set_member_name ("difficulty_catagory");
+        builder.add_string_value (board.get_difficulty_catagory ().to_string ());
+        builder.set_member_name ("time_elapsed");
+        builder.add_double_value (game.get_total_time_played ());
+
+        builder.set_member_name ("cells");
+        builder.begin_array ();
+
+        for (var i = 0; i < board.rows; i++)
+        {
+            for (var j = 0; j < board.cols; j++)
+            {
+                if (board_cells[i, j] == 0)
+                    continue;
+
+                builder.begin_object ();
+
+                builder.set_member_name ("position");
+                builder.begin_array ();
+                builder.add_int_value (i);
+                builder.add_int_value (j);
+                builder.end_array ();
+                builder.set_member_name ("value");
+                builder.add_int_value (board_cells[i, j]);
+                builder.set_member_name ("fixed");
+                builder.add_boolean_value (board.is_fixed[i, j]);
+                builder.set_member_name ("notes");
+                builder.add_string_value ("");
+
+                builder.end_object ();
+            }
+        }
+
+        builder.end_array ();
+        builder.end_object ();
+
+        Json.Generator generator = new Json.Generator ();
+        generator.set_pretty (true);
+        Json.Node root = builder.get_root ();
+        generator.set_root (root);
+
+        return generator.to_data (null);
+    }
+
+    private SudokuGame? parse_json_to_game (string file_path)
+    {
+        Json.Parser parser = new Json.Parser ();
+        try {
+            parser.load_from_file (file_path);
+        } catch (Error e) {
+            return null;
+        }
+
+        var board = new SudokuBoard ();
+        Json.Node node = parser.get_root ();
+        Json.Reader reader = new Json.Reader (node);
+        reader.read_member ("cells");
+        assert (reader.is_array ());
+
+        for (var i = 0; i < reader.count_elements (); i++)
+        {
+            reader.read_element (i);   // Reading a cell
+
+            reader.read_member ("position");
+            return_val_if_fail (reader.is_array (), null);
+            return_val_if_fail (reader.count_elements () == 2, null);
+            reader.read_element (0);
+            return_val_if_fail (reader.is_value (), null);
+            var row = (int) reader.get_int_value ();
+            reader.end_element ();
+
+            reader.read_element (1);
+            return_val_if_fail (reader.is_value (), null);
+            var col = (int) reader.get_int_value ();
+            reader.end_element ();
+            reader.end_member ();
+
+            reader.read_member ("value");
+            return_val_if_fail (reader.is_value (), null);
+            var val = (int) reader.get_int_value ();
+            reader.end_member ();
+
+            reader.read_member ("fixed");
+            return_val_if_fail (reader.is_value (), null);
+            var is_fixed = reader.get_boolean_value ();
+            reader.end_member ();
+
+            reader.end_element ();
+
+            board.insert (row, col, val, is_fixed);
+        }
+        reader.end_member ();
+
+        reader.read_member ("time_elapsed");
+        return_val_if_fail (reader.is_value (), null);
+        board.previous_played_time = reader.get_double_value ();
+        reader.end_member ();
+
+        return new SudokuGame (board);
+    }
+}


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