[five-or-more/gsoc-vala-port: 11/29] Port game to Vala



commit 925ed77f976194f6990a9745f6d7913956e9087c
Author: ruxandraS <ruxandrasimion93 gmail com>
Date:   Tue Jul 31 14:11:25 2018 +0000

    Port game to Vala

 data/five-or-more-vala-preferences.ui |   1 +
 data/five-or-more-vala.ui             |   5 +-
 src-vala/board.vala                   | 414 ++++++++++++++++++++++++++++++++++
 src-vala/game.vala                    | 221 ++++++++++++++++--
 src-vala/main.vala                    |  18 +-
 src-vala/meson.build                  |   2 +
 src-vala/next-pieces-widget.vala      |  59 +++--
 src-vala/piece-generator.vala         |  14 +-
 src-vala/piece.vala                   |  10 +-
 src-vala/preferences-dialog.vala      |  60 ++++-
 src-vala/theme-renderer.vala          |  59 ++---
 src-vala/view.vala                    | 157 ++++++++++++-
 src-vala/window.vala                  |  88 ++++++--
 13 files changed, 967 insertions(+), 141 deletions(-)
---
diff --git a/data/five-or-more-vala-preferences.ui b/data/five-or-more-vala-preferences.ui
index 5637215..d46975d 100644
--- a/data/five-or-more-vala-preferences.ui
+++ b/data/five-or-more-vala-preferences.ui
@@ -7,6 +7,7 @@
     <property name="destroy_with_parent">True</property>
     <property name="type_hint">dialog</property>
     <property name="use-header-bar">1</property>
+    <property name="border-width">18</property>
     <child internal-child="headerbar">
       <object class="GtkHeaderBar">
         <property name="title" translatable="yes">Preferences</property>
diff --git a/data/five-or-more-vala.ui b/data/five-or-more-vala.ui
index 45076fa..e882839 100644
--- a/data/five-or-more-vala.ui
+++ b/data/five-or-more-vala.ui
@@ -2,12 +2,12 @@
 <interface>
   <!-- interface-requires gtk+ 3.0 -->
   <object class="GtkAccelGroup" id="accelgroup"/>
-  <template class="FiveOrMoreWindow" parent="GtkApplicationWindow">
+  <template class="GameWindow" parent="GtkApplicationWindow">
     <property name="can_focus">False</property>
     <property name="default_width">320</property>
     <property name="default_height">400</property>
     <property name="icon_name">glines</property>
-    <property name="border-width">20</property>
+    <property name="border-width">18</property>
     <accel-groups>
       <group name="accelgroup"/>
     </accel-groups>
@@ -70,6 +70,7 @@
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="orientation">horizontal</property>
+        <property name="spacing">12</property>
         <child>
           <object class="GtkButton" id="new_game_button">
                 <property name="visible">True</property>
diff --git a/src-vala/board.vala b/src-vala/board.vala
new file mode 100644
index 0000000..0212f72
--- /dev/null
+++ b/src-vala/board.vala
@@ -0,0 +1,414 @@
+public class Board
+{
+    private const int MOVE_COST = 1;
+
+    private Cell[,] grid = null;
+    private int n_rows;
+    private int n_cols;
+
+    public signal void grid_changed ();
+
+    private Cell src;
+    private Cell dst;
+
+    private Gee.ArrayList<Cell> open = null;
+    public Gee.ArrayList<Cell> closed = null;
+    public Gee.ArrayList<Cell>? path = null;
+
+    public Board (int n_rows, int n_cols)
+    {
+        this.n_rows = n_rows;
+        this.n_cols =n_cols;
+
+        grid = new Cell[n_rows, n_cols];
+        for (int col = 0; col < n_cols; col++)
+        {
+            for (int row = 0; row < n_rows; row++)
+            {
+                grid[row, col] = new Cell (row, col, null, null);
+            }
+        }
+    }
+
+    public void reset (int n_rows, int n_cols)
+    {
+        this.n_rows = n_rows;
+        this.n_cols = n_cols;
+
+        grid = new Cell[n_rows, n_cols];
+
+        for (int col = 0; col < n_cols; col++)
+        {
+            for (int row = 0; row < n_rows; row++)
+            {
+                if (grid[row, col] != null)
+                {
+                    grid[row, col].parent = null;
+                    grid[row, col].piece = null;
+                    grid[row, col].cost = int.MAX;
+                }
+                else
+                    grid[row, col] = new Cell (row, col, null, null);
+            }
+        }
+    }
+
+    public Cell[,]? get_grid ()
+    {
+        return this.grid;
+    }
+
+    public void set_piece (int row, int col, Piece? piece)
+    {
+        grid[row, col].piece = piece;
+    }
+
+    public Piece? get_piece (int row, int col)
+    {
+        return grid[row, col].piece;
+    }
+
+    public Cell? get_cell (int row, int col)
+    {
+        return grid[row, col];
+    }
+
+    public Gee.ArrayList<Cell> find_path (int start_row,
+                                          int start_col,
+                                          int end_row,
+                                          int end_col)
+    {
+        reset_path_search ();
+
+        src = grid[start_row, start_col];
+        dst = grid[end_row, end_col];
+
+        closed = new Gee.ArrayList<Cell> (cell_equal);
+
+        open = new Gee.ArrayList<Cell> (cell_equal);
+        open.add (src);
+
+        Cell? current_cell = null;
+        int current_cost = 0;
+        Gee.ArrayList<Cell>? neighbours;
+
+        do
+        {
+            current_cost = closed.size;
+
+            current_cell = best_candidate (open, current_cost, dst);
+            closed.add (current_cell);
+            open.remove (current_cell);
+
+            if (closed.contains (dst))
+            {
+                stderr.printf ("[DEBUG]: Foud path\n");
+                for (Cell? p = dst; p != null; p = p.parent)
+                {
+                    path.insert (0, p);
+                }
+                return path;
+            }
+
+            neighbours = current_cell.get_neighbours (grid, n_rows, n_cols);
+            foreach (Cell neighbour in neighbours)
+            {
+                // if this adjacent square is already in the closed list ignore it
+                if (closed.contains (neighbour))
+                {
+                    continue;
+                }
+
+                // if its not in the open list add it
+                if (!open.contains (neighbour))
+                {
+                    neighbour.parent = current_cell;
+                    open.add (neighbour);
+                }
+
+                // if its already in the open list and using the current score makes it lower,
+                // update the parent because it means it is a better path
+                else
+                {
+                    if (total_cost (neighbour, dst, current_cost) < neighbour.cost)
+                    {
+                        neighbour.parent = current_cell;
+                        neighbour.cost = total_cost (neighbour, dst, current_cost);
+                    }
+                }
+            }
+        } while (open.size != 0);
+
+        stderr.printf ("[DEBUG]: There is no path!\n");
+
+        return path;
+    }
+
+    private bool cell_equal (Cell? a, Cell? b)
+    {
+        return (a != null && b != null) ? a.equal (b) : false;
+    }
+
+    private void reset_path_search ()
+    {
+        if (path != null)
+            path.clear ();
+        else
+            path = new Gee.ArrayList<Cell> ();
+
+        foreach (Cell cell in grid)
+        {
+            cell.parent = null;
+            cell.cost = int.MAX;
+        }
+    }
+
+    // f = g + h, where f is the cost of the road
+    // g is the movement cost from the start cell to the current square
+    // h is the estimated movement cost from the current square to the destination cell
+    private Cell? best_candidate (Gee.ArrayList<Cell> neighbours, int current_cost, Cell end)
+    {
+        int lowest_f = int.MAX;
+        Cell? best_candidate = null;
+
+        foreach (Cell neighbour in neighbours)
+        {
+            neighbour.cost = total_cost (neighbour, end, current_cost);
+
+            if (neighbour.cost < lowest_f)
+            {
+                lowest_f = neighbour.cost;
+                best_candidate = neighbour;
+            }
+        }
+
+        return best_candidate;
+    }
+
+    // for h it is used the Manhattan distance
+    // the sum of the absolute values of the differences of the coordinates.
+    // if start = (start_x, start_y) and end = (end_x, end_y)
+    // => h = |start_x - end_x| + |start_y - end_y|
+    private int manhattan (int start_x, int start_y, int end_x, int end_y)
+    {
+        return (start_x - end_x).abs () + (start_y - end_y).abs ();
+    }
+
+    private int total_cost (Cell start, Cell end, int current_cost)
+    {
+        int f, g, h;
+
+        g = current_cost + MOVE_COST;
+        h = manhattan (start.row, start.col, end.row, end.col);
+        f = g + h;
+
+        return f;
+    }
+}
+
+public class Cell
+{
+    public int row;
+    public int col;
+    public Cell? parent;
+    public Piece? piece;
+    public int cost;
+
+    public Cell (int row, int col, Cell? parent, Piece? piece)
+    {
+        this.row = row;
+        this.col = col;
+        this.parent = parent;
+        this.piece = piece;
+        this.cost = int.MAX;
+    }
+
+    public bool equal (Cell cell)
+    {
+        return this.row == cell.row && this.col == cell.col;
+    }
+
+    public void print_cell (string messg, Cell? c)
+    {
+        if (c != null)
+            stderr.printf ("%s: %d %d\n", messg, c.row, c.col);
+        else
+            stderr.printf ("%s: null\n", messg);
+    }
+
+    private Cell get_neighbour (Cell[,] board, Direction dir, int n_rows, int n_cols)
+    {
+        Cell? neighbour = null;
+        int row = -1, col = -1;
+
+        switch (dir)
+        {
+            case Direction.RIGHT:
+                row = this.row;
+                col = this.col + 1;
+                break;
+            case Direction.LEFT:
+                row = this.row;
+                col = this.col - 1;
+                break;
+            case Direction.UP:
+                row = this.row - 1;
+                col = this.col;
+                break;
+            case Direction.DOWN:
+                row = this.row + 1;
+                col = this.col;
+                break;
+            case Direction.UPPER_LEFT:
+                row = this.row - 1;
+                col = this.col - 1;
+                break;
+            case Direction.LOWER_RIGHT:
+                row = this.row + 1;
+                col = this.col + 1;
+                break;
+            case Direction.UPPER_RIGHT:
+                row = this.row - 1;
+                col = this.col + 1;
+                break;
+            case Direction.LOWER_LEFT:
+                row = this.row + 1;
+                col = this.col - 1;
+                break;
+        }
+
+        if (row >= 0 && row < n_rows &&
+            col >= 0 && col < n_cols)
+            neighbour = board[row, col];
+
+        return neighbour;
+    }
+
+    public Gee.ArrayList<Cell> get_neighbours (Cell[,] board, int n_rows, int n_cols)
+    {
+        Gee.ArrayList<Cell> neighbours = new Gee.ArrayList<Cell> ();
+        Cell? right = null, left = null, up = null, down = null;
+
+        right = this.get_neighbour (board, Direction.RIGHT, n_rows, n_cols);
+        if (right != null && right.piece == null)
+            neighbours.add (right);
+
+        left = get_neighbour (board, Direction.LEFT, n_rows, n_cols);
+        if (left != null && left.piece == null)
+            neighbours.add (left);
+
+        up = get_neighbour (board, Direction.UP, n_rows, n_cols);
+        if (up != null && up.piece == null)
+            neighbours.add (up);
+
+        down = get_neighbour (board, Direction.DOWN, n_rows, n_cols);
+        if (down != null && down.piece == null)
+            neighbours.add (down);
+
+        return neighbours;
+    }
+
+    private void get_direction (Cell[,] board, int n_rows, int n_cols, Direction dir, ref 
Gee.ArrayList<Cell>? list)
+    {
+        if (list == null)
+            list = new Gee.ArrayList<Cell> ();
+
+        for (Cell? cell = this;
+            cell != null && cell.piece != null && cell.piece.equal (this.piece);
+            cell = cell.get_neighbour (board, dir, n_rows, n_cols))
+        {
+            if (!list.contains (cell))
+                list.add (cell);
+        }
+    }
+
+    private Gee.ArrayList<Cell> get_horizontal (Cell[,] board, int n_rows, int n_cols)
+    {
+        Gee.ArrayList<Cell>? list = null;
+
+        get_direction (board, n_rows, n_cols, Direction.LEFT, ref list);
+        get_direction (board, n_rows, n_cols, Direction.RIGHT, ref list);
+
+        return list;
+    }
+
+    private Gee.ArrayList<Cell> get_vertical (Cell[,] board, int n_rows, int n_cols)
+    {
+        Gee.ArrayList<Cell>? list = null;
+
+        get_direction (board, n_rows, n_cols, Direction.UP, ref list);
+        get_direction (board, n_rows, n_cols, Direction.DOWN, ref list);
+
+        return list;
+    }
+
+    private Gee.ArrayList<Cell> get_first_diagonal (Cell[,] board, int n_rows, int n_cols)
+    {
+        Gee.ArrayList<Cell>? list = null;
+
+        get_direction (board, n_rows, n_cols, Direction.UPPER_LEFT, ref list);
+        get_direction (board, n_rows, n_cols, Direction.LOWER_RIGHT, ref list);
+
+        return list;
+    }
+
+    private Gee.ArrayList<Cell> get_second_diagonal (Cell[,] board, int n_rows, int n_cols)
+    {
+        Gee.ArrayList<Cell>? list = null;
+
+        get_direction (board, n_rows, n_cols, Direction.UPPER_RIGHT, ref list);
+        get_direction (board, n_rows, n_cols, Direction.LOWER_LEFT, ref list);
+
+        return list;
+    }
+
+    public Gee.HashSet<Cell> get_all_directions (Cell[,] board, int n_rows, int n_cols)
+    {
+        Gee.ArrayList<Cell>? list;
+        Gee.HashSet<Cell>? inactivate = new Gee.HashSet<Cell> ();
+
+        list = get_horizontal (board, n_rows, n_cols);
+        if (list.size >= Game.N_MATCH)
+        {
+            foreach (var l in list)
+                inactivate.add (l);
+        }
+
+        list = get_vertical (board, n_rows, n_cols);
+        if (list.size >= Game.N_MATCH)
+        {
+            foreach (var l in list)
+                inactivate.add (l);
+        }
+
+        list = get_first_diagonal (board, n_rows, n_cols);
+        if (list.size >= Game.N_MATCH)
+        {
+            foreach (var l in list)
+                inactivate.add (l);
+        }
+
+        list = get_second_diagonal (board, n_rows, n_cols);
+        if (list.size >= Game.N_MATCH)
+        {
+            foreach (var l in list)
+                inactivate.add (l);
+        }
+
+        stderr.printf ("To be inactivated: %d\n", inactivate.size);
+
+        return inactivate;
+    }
+}
+
+enum Direction
+{
+    RIGHT,
+    LEFT,
+    UP,
+    DOWN,
+    UPPER_RIGHT,
+    LOWER_LEFT,
+    UPPER_LEFT,
+    LOWER_RIGHT,
+}
diff --git a/src-vala/game.vala b/src-vala/game.vala
index 5ce6af7..d4ae94e 100644
--- a/src-vala/game.vala
+++ b/src-vala/game.vala
@@ -1,24 +1,51 @@
-namespace FiveOrMore
-{
-
 public class Game : Object
 {
     public const int N_TYPES = 7;
     public const int N_ANIMATIONS = 4;
+    public const int N_MATCH = 5;
 
     private Settings settings;
 
-    private Gdk.RGBA background_color;
     private int size;
     private NextPiecesGenerator next_pieces_generator;
 
-    public signal Gee.ArrayList<Piece> set_next_pieces_queue (Gee.ArrayList<Piece> next_pieces_queue);
+    public Board board = null;
+    public int n_rows { get; private set; }
+    public int n_cols { get; private set; }
+    public int n_next_pieces;
+
+    private int n_cells;
+    public int n_filled_cells;
+
+    public Gee.ArrayList<Cell>? current_path = null;
+    public bool animating = false;
+    public Piece animating_piece;
+
+    public int score { get; private set; }
+
+    public signal void current_path_cell_pos_changed ();
+    private int _current_path_cell_pos = -1;
+    public int current_path_cell_pos
+    {
+        get { return _current_path_cell_pos; }
+        set
+        {
+            _current_path_cell_pos = value;
+            current_path_cell_pos_changed ();
+        }
+    }
+
+    public signal void queue_changed (Gee.ArrayList<Piece> next_pieces_queue);
 
     private Gee.ArrayList<Piece> _next_pieces_queue;
     public Gee.ArrayList<Piece> next_pieces_queue
     {
         get { return _next_pieces_queue; }
-        set { _next_pieces_queue = set_next_pieces_queue (value); }
+        set
+        {
+            _next_pieces_queue = value;
+            queue_changed (_next_pieces_queue);
+        }
     }
 
     const GameDifficulty[] game_difficulty = {
@@ -28,31 +55,187 @@ public class Game : Object
         { 20, 15, 7, 7 }
     };
 
+    public const KeyValue scorecats[] = {
+        { "Small",  NC_("board size", "Small")  },
+        { "Medium", NC_("board size", "Medium") },
+        { "Large",  NC_("board size", "Large")  }
+    };
+
+    public signal void game_over ();
+    public int n_categories = 3;
+    public string score_current_category = null;
+
     public Game (Settings settings)
     {
         this.settings = settings;
 
-        background_color = Gdk.RGBA ();
-        var color_str = settings.get_string (FiveOrMoreApp.KEY_BACKGROUND_COLOR);
-        background_color.parse (color_str);
-        settings.changed[FiveOrMoreApp.KEY_BACKGROUND_COLOR].connect (() => {
-            color_str = settings.get_string (FiveOrMoreApp.KEY_BACKGROUND_COLOR);
-            background_color.parse (color_str);
-        });
-
         size = settings.get_int (FiveOrMoreApp.KEY_SIZE);
         settings.changed[FiveOrMoreApp.KEY_SIZE].connect (() => {
             size = settings.get_int (FiveOrMoreApp.KEY_SIZE);
+            restart ();
         });
 
+        init_game ();
+    }
+
+    private void init_game ()
+    {
+        this.n_rows = game_difficulty[size].n_rows;
+        this.n_cols = game_difficulty[size].n_cols;
+        this.n_next_pieces = game_difficulty[size].n_next_pieces;
+
+        this.n_cells = n_rows * n_cols;
+        this.n_filled_cells = 0;
+
+        this.score = 0;
+        this.score_current_category = scorecats[size - 1].key;
+
         next_pieces_generator = new NextPiecesGenerator (game_difficulty[size].n_next_pieces,
                                                          game_difficulty[size].n_types);
-        next_pieces_queue = null;
+        generate_next_pieces ();
+
+        if (board == null)
+            board = new Board (this.n_rows, this.n_cols);
+        else
+            board.reset (n_rows, n_cols);
+
+        fill_board (this.n_rows, this.n_cols);
+
+        generate_next_pieces ();
     }
 
     public void generate_next_pieces ()
     {
-        next_pieces_queue = next_pieces_generator.yield_next_pieces ();
+        this.next_pieces_queue = this.next_pieces_generator.yield_next_pieces ();
+    }
+
+    private void fill_board (int n_rows, int n_cols)
+    {
+        int row = -1, col = -1;
+
+        for (int i = 0; i < next_pieces_queue.size; i++)
+        {
+            do
+            {
+                row = GLib.Random.int_range (0, n_rows);
+                col = GLib.Random.int_range (0, n_cols);
+            } while (board.get_piece (row, col) != null);
+
+            board.set_piece (row, col, next_pieces_queue [i]);
+
+            Gee.HashSet<Cell> inactivate =
+            board.get_cell (row, col).get_all_directions (board.get_grid (),
+                                                        n_rows,
+                                                        n_cols);
+            if (inactivate.size > 0)
+            {
+                n_filled_cells -= inactivate.size;
+                foreach (Cell cell in inactivate)
+                {
+                    board.set_piece (cell.row, cell.col, null);
+                }
+
+                update_score (inactivate.size);
+            }
+
+            board.grid_changed ();
+            n_filled_cells ++;
+
+            if (check_game_over ())
+            {
+                board.grid_changed ();
+                return;
+            }
+        }
+    }
+
+    private void update_score (int n_matched)
+    {
+        score += (int) (45 * Math.log (0.25 * n_matched));
+    }
+
+    private bool check_game_over ()
+    {
+        if (n_cells - n_filled_cells == 0)
+        {
+            game_over ();
+            return true;
+        }
+
+        return false;
+    }
+
+    public void next_step ()
+    {
+        fill_board (this.n_rows, this.n_cols);
+        generate_next_pieces ();
+    }
+
+    public bool make_move (int start_row, int start_col, int end_row, int end_col)
+    {
+        current_path = board.find_path (start_row,
+                                        start_col,
+                                        end_row,
+                                        end_col);
+
+        if (current_path == null || current_path.size == 0)
+        {
+            return false;
+        }
+
+        current_path_cell_pos = 0;
+        animating_piece = current_path.get (current_path_cell_pos).piece;
+        Timeout.add (20, animate);
+
+        return true;
+    }
+
+    public bool animate ()
+    {
+        animating = true;
+
+        Cell curr_cell = current_path[current_path_cell_pos];
+
+        if (current_path_cell_pos == 0)
+            board.set_piece (curr_cell.row, curr_cell.col, null);
+
+        if (current_path_cell_pos == current_path.size - 1)
+        {
+            board.set_piece (curr_cell.row, curr_cell.col, animating_piece);
+
+            current_path = null;
+            var inactivate =
+                curr_cell.get_all_directions (board.get_grid (), n_rows, n_cols);
+
+            if (inactivate.size > 0)
+            {
+                n_filled_cells -= inactivate.size;
+                foreach (Cell cell in inactivate)
+                {
+                    board.set_piece (cell.row, cell.col, null);
+                }
+
+                update_score (inactivate.size);
+                stderr.printf ("[DEBUG] New score is %d", score);
+            }
+
+            if (inactivate.size < Game.N_MATCH)
+                next_step ();
+
+            board.grid_changed ();
+            animating = false;
+
+            return Source.REMOVE;
+        }
+
+        current_path_cell_pos++;
+
+        return Source.CONTINUE;
+    }
+
+    public void restart ()
+    {
+        init_game ();
     }
 }
 
@@ -73,4 +256,8 @@ struct GameDifficulty
     public int n_next_pieces;
 }
 
-} // namespace FiveOrMore
+struct KeyValue
+{
+  public string key;
+  public string name;
+}
diff --git a/src-vala/main.vala b/src-vala/main.vala
index 1ea2441..201e833 100644
--- a/src-vala/main.vala
+++ b/src-vala/main.vala
@@ -1,5 +1,3 @@
-namespace FiveOrMore
-{
 public class FiveOrMoreApp: Gtk.Application
 {
     public const string KEY_SIZE = "size";
@@ -7,7 +5,7 @@ public class FiveOrMoreApp: Gtk.Application
 
     private Settings settings;
 
-    private Gtk.ApplicationWindow window;
+    private GameWindow window;
     private PreferencesDialog? preferences_dialog = null;
 
     private const GLib.ActionEntry action_entries[] =
@@ -35,7 +33,7 @@ public class FiveOrMoreApp: Gtk.Application
         base.startup ();
 
         settings = new Settings ("org.gnome.five-or-more");
-        window = new FiveOrMore.Window (this, settings);
+        window = new GameWindow (this, settings);
 
         add_action_entries (action_entries, this);
     }
@@ -51,12 +49,18 @@ public class FiveOrMoreApp: Gtk.Application
 
     private void new_game_cb ()
     {
-
+        stderr.printf ("[DEBUG] Pressed New Game!\n");
+        if (window == null)
+        {
+            warning ("Failed to restart game");
+            return;
+        }
+        window.restart_game ();
     }
 
     private void scores_cb ()
     {
-
+        window.show_scores ();
     }
 
     private void preferences_cb ()
@@ -126,5 +130,3 @@ public class FiveOrMoreApp: Gtk.Application
                                "website", "https://wiki.gnome.org/Apps/Five%20or%20more";);
     }
 }
-
-} // namespace FiveOrMore
diff --git a/src-vala/meson.build b/src-vala/meson.build
index 123ac7d..4530c55 100644
--- a/src-vala/meson.build
+++ b/src-vala/meson.build
@@ -2,6 +2,7 @@
 
 five_or_more_sources = [
   'config.vapi',
+  'board.vala',
   'game.vala',
   'main.vala',
   'next-pieces-widget.vala',
@@ -18,6 +19,7 @@ five_or_more_deps = [
   gee_dep,
   gio_dep,
   gtk_dep,
+  libgnome_games_support_dep,
   librsvg_dep,
 ]
 
diff --git a/src-vala/next-pieces-widget.vala b/src-vala/next-pieces-widget.vala
index 3a3e3bf..e90edb5 100644
--- a/src-vala/next-pieces-widget.vala
+++ b/src-vala/next-pieces-widget.vala
@@ -1,42 +1,39 @@
-namespace FiveOrMore
-{
-
 public class NextPiecesWidget : Gtk.DrawingArea
 {
+    private Settings settings;
     private Game? game;
     private ThemeRenderer? theme;
 
     private Gee.ArrayList<Piece> local_pieces_queue;
-    private int widget_height = 0;
-    private bool queue_resized = false;
+    private int widget_height = -1;
 
-    public NextPiecesWidget (Game game, ThemeRenderer theme)
+    public NextPiecesWidget (Settings settings, Game game, ThemeRenderer theme)
     {
+        this.settings = settings;
         this.game = game;
         this.theme = theme;
 
-        game.set_next_pieces_queue.connect (queue_changed);
+        set_queue_size ();
+        settings.changed[FiveOrMoreApp.KEY_SIZE].connect (() => {
+            set_queue_size ();
+            queue_draw ();
+        });
+
+        local_pieces_queue = game.next_pieces_queue;
+        queue_changed_cb (local_pieces_queue);
+
+        game.queue_changed.connect (queue_changed_cb);
     }
 
-    private Gee.ArrayList<Piece> queue_changed (Gee.ArrayList<Piece> next_pieces_queue)
+    private void set_queue_size ()
     {
-        if (!queue_resized)
-        {
-            this.set_size_request (ThemeRenderer.ELEM_WIDTH * next_pieces_queue.size, 
ThemeRenderer.ELEM_HEIGHT);
-            queue_resized = true;
-        }
+        set_size_request (ThemeRenderer.DEFAULT_SPRITE_SIZE * game.n_next_pieces, 
ThemeRenderer.DEFAULT_SPRITE_SIZE);
+    }
 
+    private void queue_changed_cb (Gee.ArrayList<Piece> next_pieces_queue)
+    {
         local_pieces_queue = next_pieces_queue;
         queue_draw ();
-
-        int i;
-        for (i = 0; i < next_pieces_queue.size; i++)
-        {
-            stderr.printf ("[DEBUG] %d\n", local_pieces_queue[i].id);
-        }
-        stderr.printf ("\n");
-
-        return next_pieces_queue;
     }
 
     public override bool draw (Cairo.Context cr)
@@ -44,7 +41,7 @@ public class NextPiecesWidget : Gtk.DrawingArea
         if (theme == null)
             return false;
 
-        if (widget_height == 0)
+        if (widget_height == -1)
         {
             widget_height = this.get_allocated_height ();
         }
@@ -54,14 +51,14 @@ public class NextPiecesWidget : Gtk.DrawingArea
         Gdk.cairo_set_source_rgba (cr, background_color);
         cr.paint ();
 
-        int i;
-        for (i = 0; i < local_pieces_queue.size; i++)
+        for (int i = 0; i < local_pieces_queue.size; i++)
         {
-            theme.render_element (cr,
-                                  local_pieces_queue[i].id,
-                                  0,
-                                  i * ThemeRenderer.ELEM_WIDTH,
-                                  (widget_height / 2.0f) - (ThemeRenderer.ELEM_HEIGHT / 2.0f));
+            theme.render_sprite (cr,
+                                 local_pieces_queue[i].id,
+                                 0,
+                                 i * ThemeRenderer.DEFAULT_SPRITE_SIZE,
+                                 (widget_height / 2) - (ThemeRenderer.DEFAULT_SPRITE_SIZE / 2),
+                                 ThemeRenderer.DEFAULT_SPRITE_SIZE);
 
         }
 
@@ -70,5 +67,3 @@ public class NextPiecesWidget : Gtk.DrawingArea
         return true;
     }
 }
-
-} // namespace FiveOrMore
diff --git a/src-vala/piece-generator.vala b/src-vala/piece-generator.vala
index 1da2073..7c96ccf 100644
--- a/src-vala/piece-generator.vala
+++ b/src-vala/piece-generator.vala
@@ -1,6 +1,3 @@
-namespace FiveOrMore
-{
-
 public class NextPiecesGenerator
 {
     private int n_types;
@@ -23,15 +20,18 @@ public class NextPiecesGenerator
     {
         this.pieces.clear ();
 
-        int i;
-        for (i = 0; i < this.n_next_pieces; i++)
+        for (int i = 0; i < this.n_next_pieces; i++)
         {
             int id = yield_next_piece ();
             this.pieces.add (new Piece (id));
         }
 
+        for (int i = 0; i < this.n_next_pieces; i++)
+        {
+            stderr.printf ("[DEBUG] %d\n", this.pieces[i].id);
+        }
+        stderr.printf ("\n");
+
         return this.pieces;
     }
 }
-
-} // namespace FiveOrMore
diff --git a/src-vala/piece.vala b/src-vala/piece.vala
index 31d0024..b1f7b0c 100644
--- a/src-vala/piece.vala
+++ b/src-vala/piece.vala
@@ -1,6 +1,3 @@
-namespace FiveOrMore
-{
-
 public class Piece
 {
     public int id;
@@ -9,6 +6,9 @@ public class Piece
     {
         this.id = id;
     }
-}
 
-} // namespace FiveOrMore
+    public bool equal (Piece piece)
+    {
+        return this.id == piece.id;
+    }
+}
diff --git a/src-vala/preferences-dialog.vala b/src-vala/preferences-dialog.vala
index b49398c..606bec1 100644
--- a/src-vala/preferences-dialog.vala
+++ b/src-vala/preferences-dialog.vala
@@ -1,6 +1,3 @@
-namespace FiveOrMore
-{
-
 [GtkTemplate (ui = "/org/gnome/five-or-more/ui/preferences-dialog.ui")]
 public class PreferencesDialog : Gtk.Dialog
 {
@@ -23,10 +20,53 @@ public class PreferencesDialog : Gtk.Dialog
             warning ("Failed to set color: %s", color.to_string ());
     }
 
-    private void size_cb (BoardSize size)
+    private void size_cb (Gtk.ToggleButton button, BoardSize size)
     {
-        if (!settings.set_int ("size", size))
-            warning ("Failed to set size: %d", size);
+        var game_size = settings.get_int ("size");
+
+        if (game_size == size || !button.get_active ())
+            return;
+
+        var flags = Gtk.DialogFlags.DESTROY_WITH_PARENT;
+        var restart_game_dialog = new Gtk.MessageDialog (this,
+                                                         flags,
+                                                         Gtk.MessageType.WARNING,
+                                                         Gtk.ButtonsType.NONE,
+                                                         _("Are you sure you want to restart the game?"),
+                                                         null);
+        restart_game_dialog.add_buttons (_("_Cancel"), Gtk.ResponseType.CANCEL,
+                                         _("_Restart"), Gtk.ResponseType.OK,
+                                         null);
+
+        var result = restart_game_dialog.run ();
+        restart_game_dialog.destroy ();
+        switch (result)
+        {
+            case Gtk.ResponseType.OK:
+                 if (!settings.set_int ("size", size))
+                    warning ("Failed to set size: %d", size);
+                button.set_active (true);
+                break;
+            case Gtk.ResponseType.CANCEL:
+                Gtk.ToggleButton radiobutton;
+                switch (game_size)
+                {
+                    case BoardSize.SMALL:
+                        radiobutton = radiobutton_small;
+                        break;
+                    case BoardSize.MEDIUM:
+                        radiobutton = radiobutton_medium;
+                        break;
+                    case BoardSize.LARGE:
+                        radiobutton = radiobutton_large;
+                        break;
+                    default:
+                        radiobutton = radiobutton_small;
+                        break;
+                }
+                radiobutton.set_active (true);
+                break;
+        }
     }
 
     public PreferencesDialog (Settings settings)
@@ -57,10 +97,8 @@ public class PreferencesDialog : Gtk.Dialog
                 break;
         }
 
-        radiobutton_small.toggled.connect (() => { size_cb (SMALL); });
-        radiobutton_medium.toggled.connect (() => { size_cb (MEDIUM); });
-        radiobutton_large.toggled.connect (() => { size_cb (LARGE); });
+        radiobutton_small.toggled.connect ((button) => { size_cb (button, SMALL); });
+        radiobutton_medium.toggled.connect ((button) => { size_cb (button, MEDIUM); });
+        radiobutton_large.toggled.connect ((button) => { size_cb (button, LARGE); });
     }
 }
-
-} // namespace FiveOrMore
diff --git a/src-vala/theme-renderer.vala b/src-vala/theme-renderer.vala
index e7fb505..d8ec349 100644
--- a/src-vala/theme-renderer.vala
+++ b/src-vala/theme-renderer.vala
@@ -1,75 +1,64 @@
-namespace FiveOrMore
-{
-
 public class ThemeRenderer
 {
     public const string THEME = "balls.svg";
-    public const int ELEM_WIDTH = 20;
-    public const int ELEM_HEIGHT = 20;
+    public const int DEFAULT_SPRITE_SIZE = 20;
 
     private Rsvg.Handle? theme = null;
-    private float width;
-    private float height;
+    private float sprite_sheet_width;
+    private float sprite_sheet_height;
 
     private Cairo.Pattern? tile_pattern = null;
-    private Gdk.Rectangle? preview_rect = null;
 
+    private int sprite_size = DEFAULT_SPRITE_SIZE;
+
+    private Cairo.Context cr_preview;
     public ThemeRenderer (string theme_file)
     {
         try
         {
             theme = new Rsvg.Handle.from_file (theme_file);
             var dimensions = theme.get_dimensions ();
-            width = dimensions.width;
-            height = dimensions.height;
+            sprite_sheet_width = dimensions.width;
+            sprite_sheet_height = dimensions.height;
         }
         catch (Error e)
         {
             GLib.warning ("Unable to load theme\n");
         }
-
-        preview_rect = Gdk.Rectangle ();
-        preview_rect.x = 0;
-        preview_rect.y = 0;
-        preview_rect.width = ELEM_WIDTH;
-        preview_rect.height = ELEM_HEIGHT;
     }
 
-    public void render_element (Cairo.Context cr, int type, int animation, double x, double y)
+    public void render_sprite (Cairo.Context cr, int type, int animation, double x, double y, int size)
     {
-        if (tile_pattern == null)
+        if (tile_pattern == null || sprite_size != size)
         {
+            sprite_size = size;
+
             var preview_surface = new Cairo.Surface.similar (cr.get_target (),
                                                              Cairo.Content.COLOR_ALPHA,
-                                                             Game.N_ANIMATIONS * ELEM_HEIGHT,
-                                                             Game.N_TYPES * ELEM_HEIGHT);
-            var cr_preview = new Cairo.Context (preview_surface);
-            tile_pattern = new Cairo.Pattern.for_surface (preview_surface);
+                                                             Game.N_ANIMATIONS * sprite_size,
+                                                             Game.N_TYPES * sprite_size);
+            cr_preview = new Cairo.Context (preview_surface);
 
-            Cairo.Matrix matrix = Cairo.Matrix.identity ();
-            matrix.scale (Game.N_ANIMATIONS * ELEM_WIDTH / width ,
-                          Game.N_TYPES * ELEM_HEIGHT / height);
+            var matrix = Cairo.Matrix.identity ();
+            matrix.scale (Game.N_ANIMATIONS * sprite_size / sprite_sheet_width,
+                          Game.N_TYPES * sprite_size / sprite_sheet_height);
             cr_preview.set_matrix (matrix);
+
             theme.render_cairo (cr_preview);
+
+            tile_pattern = new Cairo.Pattern.for_surface (preview_surface);
         }
 
         cr.set_source (tile_pattern);
 
-        int texture_x, texture_y;
-        get_texture_pos (type, animation, out texture_x, out texture_y);
+        var texture_x = sprite_size * animation;
+        var texture_y = sprite_size * type;
 
         var m = Cairo.Matrix.identity ();
         m.translate (texture_x - x, texture_y - y);
         tile_pattern.set_matrix (m);
 
-        cr.rectangle (x, y, ELEM_WIDTH, ELEM_HEIGHT);
+        cr.rectangle (x, y, sprite_size, sprite_size);
         cr.fill ();
     }
-
-    private void get_texture_pos (int type, int animation, out int texture_x, out int texture_y)
-    {
-        texture_x = ELEM_WIDTH * animation;;
-        texture_y = ELEM_HEIGHT * type;
-    }
 }
-} // namespace FiveOrMore
\ No newline at end of file
diff --git a/src-vala/view.vala b/src-vala/view.vala
index c765079..6c2dd9e 100644
--- a/src-vala/view.vala
+++ b/src-vala/view.vala
@@ -1,30 +1,142 @@
-namespace FiveOrMore
-{
-
 public class View : Gtk.DrawingArea
 {
+    private const int MINIMUM_BOARD_SIZE = 300;
+
+    private Settings settings;
     private Game? game = null;
     private ThemeRenderer? theme = null;
+    private Gdk.RGBA background_color;
+
+    private Gdk.Rectangle board_rectangle;
+
+    private int piece_size = 0;
+
+    private int cell_x;
+    private int cell_y;
+    private int start_x;
+    private int start_y;
+    private int end_x;
+    private int end_y;
 
-    public View (Game game, ThemeRenderer theme)
+    public View (Settings settings, Game game, ThemeRenderer theme)
     {
+        this.settings = settings;
         this.game = game;
         this.theme = theme;
 
+        background_color = Gdk.RGBA ();
+        set_background_color ();
+        settings.changed[FiveOrMoreApp.KEY_BACKGROUND_COLOR].connect (() => {
+            set_background_color ();
+            queue_draw ();
+        });
+
+        set_size_request (MINIMUM_BOARD_SIZE, MINIMUM_BOARD_SIZE);
+
+        board_rectangle = Gdk.Rectangle ();
+        board_rectangle.x = board_rectangle.y = 0;
+        update_sizes (MINIMUM_BOARD_SIZE, MINIMUM_BOARD_SIZE);
+        /* it depends on which one changes last, so it is better to call them both */
+        game.notify["n-rows"].connect (() => {
+            update_sizes (get_allocated_width (), get_allocated_height ());
+            queue_draw ();
+        });
+        game.notify["n-cols"].connect (() => {
+            update_sizes (get_allocated_width (), get_allocated_height ());
+            queue_draw ();
+        });
+
+        game.board.grid_changed.connect (board_changed_cb);
         add_events (Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
+
+        game.current_path_cell_pos_changed.connect (current_path_cell_pos_changed_cb);
+
+        start_x = -1;
+        start_y = -1;
+        end_x = -1;
+        end_y = -1;
+
     }
 
-    public override bool button_press_event (Gdk.EventButton event)
+    private void board_changed_cb ()
+    {
+        queue_draw ();
+    }
+
+    private void current_path_cell_pos_changed_cb ()
     {
+        queue_draw ();
+    }
 
-        if (game == null)
+    private void set_background_color ()
+    {
+        var color_str = settings.get_string (FiveOrMoreApp.KEY_BACKGROUND_COLOR);
+        background_color.parse (color_str);
+    }
+
+    public override bool button_press_event (Gdk.EventButton event)
+    {
+        if (game == null || game.animating)
             return false;
 
         /* Ignore the 2BUTTON and 3BUTTON events. */
         if (event.type != Gdk.EventType.BUTTON_PRESS)
             return false;
 
-        game.generate_next_pieces ();
+        cell_x = (int)event.x / piece_size;
+        cell_y = (int)event.y / piece_size;
+
+        if (cell_x >= game.n_cols || cell_y >= game.n_rows)
+            return false;
+
+        /* if selected cell is not empty, set start */
+        if (game.board.get_piece (cell_y, cell_x) != null)
+        {
+            start_x = cell_x;
+            start_y = cell_y;
+            stderr.printf ("[DEBUG]: pointA %d %d\n", start_y, start_x);
+        }
+        /* if selected cell is empty and start is set, and cell is empty, set end */
+        else if (game.board.get_piece (cell_y, cell_x) == null && start_x != -1 && start_y != -1)
+        {
+            end_x = cell_x;
+            end_y = cell_y;
+            stderr.printf ("[DEBUG]: pointB %d %d\n", end_y, end_x);
+
+            bool move = game.make_move (start_y, start_x, end_y, end_x);
+
+            if (!move)
+            {
+                start_x = -1;
+                start_y = -1;
+
+                return false;
+            }
+
+            foreach (Cell p in game.current_path)
+            {
+                stderr.printf ("[DEBUG]: Path %d %d\n", p.row, p.col);
+            }
+
+            start_x = -1;
+            start_y = -1;
+        }
+
+        return true;
+    }
+
+
+    private void update_sizes (int width, int height)
+    {
+        piece_size = (width - 1) / game.n_cols;
+        board_rectangle.width = piece_size * game.n_cols;
+        board_rectangle.height = piece_size * game.n_rows;
+    }
+
+    public override bool configure_event (Gdk.EventConfigure event)
+    {
+        update_sizes (event.width, event.height);
+        queue_draw ();
 
         return true;
     }
@@ -34,10 +146,35 @@ public class View : Gtk.DrawingArea
         if (theme == null)
             return false;
 
-        // theme.render_element (cr, 1, 0, 0, 0);
+        Gdk.cairo_set_source_rgba (cr, background_color);
+        Gdk.cairo_rectangle (cr, board_rectangle);
+        cr.fill ();
+
+        for (int row = 0; row < game.n_rows; row++)
+        {
+            for (int col = 0; col < game.n_cols; col++)
+            {
+                if (game.board.get_piece (row,col) != null)
+                    theme.render_sprite (cr,
+                                         game.board.get_piece (row,col).id,
+                                         0,
+                                         col * piece_size,
+                                         row * piece_size,
+                                         piece_size);
+            }
+        }
+
+        if (game.current_path != null && game.current_path.size > 0 && game.current_path_cell_pos != -1)
+        {
+            Cell current_cell = game.current_path[game.current_path_cell_pos];
+            theme.render_sprite (cr,
+                                 game.animating_piece.id,
+                                 0,
+                                 current_cell.col * piece_size,
+                                 current_cell.row * piece_size,
+                                 piece_size);
+        }
 
         return true;
     }
 }
-
-} // namespace FiveOrMore
diff --git a/src-vala/window.vala b/src-vala/window.vala
index 68fd268..5cc204d 100644
--- a/src-vala/window.vala
+++ b/src-vala/window.vala
@@ -1,8 +1,5 @@
-namespace FiveOrMore
-{
-
 [GtkTemplate (ui = "/org/gnome/five-or-more/ui/five-or-more-vala.ui")]
-public class Window : Gtk.ApplicationWindow
+public class GameWindow : Gtk.ApplicationWindow
 {
     [GtkChild]
     private Gtk.Box preview_hbox;
@@ -10,10 +7,17 @@ public class Window : Gtk.ApplicationWindow
     [GtkChild]
     private Gtk.Box hbox;
 
+    [GtkChild]
+    private Gtk.Label scorelabel;
+
+    private Games.GridFrame grid_frame;
+
     private Game? game = null;
     private ThemeRenderer? theme = null;
 
-    public Window (Gtk.Application app, Settings settings)
+    private Games.Scores.Context highscores;
+
+    public GameWindow (Gtk.Application app, Settings settings)
     {
         Object (application: app);
 
@@ -22,18 +26,74 @@ public class Window : Gtk.ApplicationWindow
         var theme_file = Path.build_filename (DATA_DIRECTORY, "themes", ThemeRenderer.THEME);
         theme = new ThemeRenderer (theme_file);
 
-        NextPiecesWidget next_pieces_widget = new NextPiecesWidget (game, theme);
-
-        game.generate_next_pieces ();
+        NextPiecesWidget next_pieces_widget = new NextPiecesWidget (settings, game, theme);
         preview_hbox.pack_start (next_pieces_widget);
         next_pieces_widget.realize ();
-        next_pieces_widget.set_visible (true);
+        next_pieces_widget.show ();
+
+        grid_frame = new Games.GridFrame (game.n_cols, game.n_rows);
+        /* it depends on which one changes last, so it is better to call them both */
+        game.notify["n-cols"].connect ((s, p) => { grid_frame.set (game.n_cols, game.n_rows); });
+        game.notify["n-rows"].connect ((s, p) => { grid_frame.set (game.n_cols, game.n_rows); });
+        game.notify["score"].connect ((s, p) => { scorelabel.set_text (game.score.to_string ()); });
+        hbox.pack_start (grid_frame);
 
-        View game_view = new View (game, theme);
-        hbox.pack_start (game_view);
-        game_view.set_visible (true);
+        View game_view = new View (settings, game, theme);
+        grid_frame.add (game_view);
+        game_view.show ();
 
+        grid_frame.show ();
+
+        var importer = new Games.Scores.DirectoryImporter ();
+        highscores = new Games.Scores.Context.with_importer ("five-or-more",
+                                                _("Board Size: "),
+                                                this,
+                                                create_category_from_key,
+                                                Games.Scores.Style.POINTS_GREATER_IS_BETTER,
+                                                importer);
+        game.game_over.connect (score_cb);
+    }
+
+    private void score_cb ()
+    {
+
+        string name = category_name_from_key (game.score_current_category);
+        var current_category = new Games.Scores.Category (game.score_current_category, name);
+        highscores.add_score (game.score,
+                              current_category,
+                              new Cancellable ());
+
+        show_scores ();
     }
-}
 
-} // namespace FiveOrMore
+    public void restart_game ()
+    {
+        game.restart ();
+    }
+
+    public void show_scores ()
+    {
+        highscores.run_dialog ();
+    }
+
+
+    private Games.Scores.Category create_category_from_key (string key)
+    {
+        string name = category_name_from_key (key);
+        if (name == null)
+            return (Games.Scores.Category) null;
+
+        return new Games.Scores.Category (key, name);
+    }
+
+    private string category_name_from_key (string key)
+    {
+        for (int i = 0; i < game.n_categories; i++) {
+            if (strcmp (Game.scorecats[i].key, key) == 0)
+            {
+                return Game.scorecats[i].name;
+            }
+        }
+        return (string) null;
+    }
+}



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