[five-or-more/gsoc-vala-port: 11/29] Port game to Vala
- From: Robert Roth <robertroth src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [five-or-more/gsoc-vala-port: 11/29] Port game to Vala
- Date: Mon, 13 Aug 2018 05:50:07 +0000 (UTC)
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]