forked from AuroraMiddleware/gtk
c766e31e8f
Make sure the callback functions in the .ui files are exported, so that those functions can be recognized at runtime so that things run properly.
474 lines
11 KiB
C
474 lines
11 KiB
C
/* Lists/Minesweeper
|
|
* #Keywords: GtkGridView, GListModel, game
|
|
*
|
|
* This demo shows how to develop a user interface for small game using a
|
|
* grid view.
|
|
*
|
|
* It demonstrates how to use the activate signal and single-press behavior
|
|
* to implement rather different interaction behavior to a typical list.
|
|
*/
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
/*** The cell object ***/
|
|
|
|
/* Create an object that holds the data for a cell in the game */
|
|
typedef struct _SweeperCell SweeperCell;
|
|
struct _SweeperCell
|
|
{
|
|
GObject parent_instance;
|
|
|
|
gboolean is_mine;
|
|
gboolean is_visible;
|
|
guint neighbor_mines;
|
|
};
|
|
|
|
enum {
|
|
CELL_PROP_0,
|
|
CELL_PROP_LABEL,
|
|
|
|
N_CELL_PROPS
|
|
};
|
|
|
|
#define SWEEPER_TYPE_CELL (sweeper_cell_get_type ())
|
|
G_DECLARE_FINAL_TYPE (SweeperCell, sweeper_cell, SWEEPER, CELL, GObject);
|
|
|
|
G_DEFINE_TYPE (SweeperCell, sweeper_cell, G_TYPE_OBJECT);
|
|
static GParamSpec *cell_properties[N_CELL_PROPS] = { NULL, };
|
|
|
|
static const char *
|
|
sweeper_cell_get_label (SweeperCell *self)
|
|
{
|
|
static const char *minecount_labels[10] = { "", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
|
|
|
|
if (!self->is_visible)
|
|
return "?";
|
|
|
|
if (self->is_mine)
|
|
return "💣";
|
|
|
|
return minecount_labels[self->neighbor_mines];
|
|
}
|
|
|
|
static void
|
|
sweeper_cell_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
SweeperCell *self = SWEEPER_CELL (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case CELL_PROP_LABEL:
|
|
g_value_set_string (value, sweeper_cell_get_label (self));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sweeper_cell_class_init (SweeperCellClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->get_property = sweeper_cell_get_property;
|
|
|
|
cell_properties[CELL_PROP_LABEL] =
|
|
g_param_spec_string ("label",
|
|
"label",
|
|
"label to display for this row",
|
|
NULL,
|
|
G_PARAM_READABLE);
|
|
|
|
g_object_class_install_properties (gobject_class, N_CELL_PROPS, cell_properties);
|
|
}
|
|
|
|
static void
|
|
sweeper_cell_init (SweeperCell *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
sweeper_cell_reveal (SweeperCell *self)
|
|
{
|
|
if (self->is_visible)
|
|
return;
|
|
|
|
self->is_visible = TRUE;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), cell_properties[CELL_PROP_LABEL]);
|
|
}
|
|
|
|
static SweeperCell *
|
|
sweeper_cell_new (void)
|
|
{
|
|
return g_object_new (SWEEPER_TYPE_CELL, NULL);
|
|
}
|
|
|
|
/*** The board object ***/
|
|
|
|
/* Create an object that holds the data for the game */
|
|
typedef struct _SweeperGame SweeperGame;
|
|
struct _SweeperGame
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GPtrArray *cells;
|
|
guint width;
|
|
guint height;
|
|
gboolean playing;
|
|
gboolean win;
|
|
};
|
|
|
|
enum {
|
|
GAME_PROP_0,
|
|
GAME_PROP_HEIGHT,
|
|
GAME_PROP_PLAYING,
|
|
GAME_PROP_WIDTH,
|
|
GAME_PROP_WIN,
|
|
|
|
N_GAME_PROPS
|
|
};
|
|
|
|
#define SWEEPER_TYPE_GAME (sweeper_game_get_type ())
|
|
G_DECLARE_FINAL_TYPE (SweeperGame, sweeper_game, SWEEPER, GAME, GObject);
|
|
|
|
static GType
|
|
sweeper_game_list_model_get_item_type (GListModel *model)
|
|
{
|
|
return SWEEPER_TYPE_GAME;
|
|
}
|
|
|
|
static guint
|
|
sweeper_game_list_model_get_n_items (GListModel *model)
|
|
{
|
|
SweeperGame *self = SWEEPER_GAME (model);
|
|
|
|
return self->width * self->height;
|
|
}
|
|
|
|
static gpointer
|
|
sweeper_game_list_model_get_item (GListModel *model,
|
|
guint position)
|
|
{
|
|
SweeperGame *self = SWEEPER_GAME (model);
|
|
|
|
return g_object_ref (g_ptr_array_index (self->cells, position));
|
|
}
|
|
|
|
static void
|
|
sweeper_game_list_model_init (GListModelInterface *iface)
|
|
{
|
|
iface->get_item_type = sweeper_game_list_model_get_item_type;
|
|
iface->get_n_items = sweeper_game_list_model_get_n_items;
|
|
iface->get_item = sweeper_game_list_model_get_item;
|
|
}
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (SweeperGame, sweeper_game, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, sweeper_game_list_model_init))
|
|
|
|
static GParamSpec *game_properties[N_GAME_PROPS] = { NULL, };
|
|
|
|
static void
|
|
sweeper_game_dispose (GObject *object)
|
|
{
|
|
SweeperGame *self = SWEEPER_GAME (object);
|
|
|
|
g_clear_pointer (&self->cells, g_ptr_array_unref);
|
|
|
|
G_OBJECT_CLASS (sweeper_game_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
sweeper_game_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
SweeperGame *self = SWEEPER_GAME (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case GAME_PROP_HEIGHT:
|
|
g_value_set_uint (value, self->height);
|
|
break;
|
|
|
|
case GAME_PROP_PLAYING:
|
|
g_value_set_boolean (value, self->playing);
|
|
break;
|
|
|
|
case GAME_PROP_WIDTH:
|
|
g_value_set_uint (value, self->width);
|
|
break;
|
|
|
|
case GAME_PROP_WIN:
|
|
g_value_set_boolean (value, self->win);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sweeper_game_class_init (SweeperGameClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->dispose = sweeper_game_dispose;
|
|
gobject_class->get_property = sweeper_game_get_property;
|
|
|
|
game_properties[GAME_PROP_HEIGHT] =
|
|
g_param_spec_uint ("height",
|
|
"height",
|
|
"height of the game grid",
|
|
1, G_MAXUINT, 8,
|
|
G_PARAM_READABLE);
|
|
|
|
game_properties[GAME_PROP_PLAYING] =
|
|
g_param_spec_boolean ("playing",
|
|
"playing",
|
|
"if the game is still going on",
|
|
FALSE,
|
|
G_PARAM_READABLE);
|
|
|
|
game_properties[GAME_PROP_WIDTH] =
|
|
g_param_spec_uint ("width",
|
|
"width",
|
|
"width of the game grid",
|
|
1, G_MAXUINT, 8,
|
|
G_PARAM_READABLE);
|
|
|
|
game_properties[GAME_PROP_WIN] =
|
|
g_param_spec_boolean ("win",
|
|
"win",
|
|
"if the game was won",
|
|
FALSE,
|
|
G_PARAM_READABLE);
|
|
|
|
g_object_class_install_properties (gobject_class, N_GAME_PROPS, game_properties);
|
|
}
|
|
|
|
static void
|
|
sweeper_game_reset_board (SweeperGame *self,
|
|
guint width,
|
|
guint height)
|
|
{
|
|
guint i;
|
|
|
|
g_ptr_array_set_size (self->cells, 0);
|
|
|
|
for (i = 0; i < width * height; i++)
|
|
{
|
|
g_ptr_array_add (self->cells, sweeper_cell_new ());
|
|
}
|
|
|
|
if (self->width != width)
|
|
{
|
|
self->width = width;
|
|
g_object_notify_by_pspec (G_OBJECT (self), game_properties[GAME_PROP_WIDTH]);
|
|
}
|
|
if (self->height != height)
|
|
{
|
|
self->height = height;
|
|
g_object_notify_by_pspec (G_OBJECT (self), game_properties[GAME_PROP_HEIGHT]);
|
|
}
|
|
if (!self->playing)
|
|
{
|
|
self->playing = TRUE;
|
|
g_object_notify_by_pspec (G_OBJECT (self), game_properties[GAME_PROP_PLAYING]);
|
|
}
|
|
if (self->win)
|
|
{
|
|
self->win = FALSE;
|
|
g_object_notify_by_pspec (G_OBJECT (self), game_properties[GAME_PROP_WIN]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sweeper_game_place_mines (SweeperGame *self,
|
|
guint n_mines)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < n_mines; i++)
|
|
{
|
|
SweeperCell *cell;
|
|
|
|
do {
|
|
cell = g_ptr_array_index (self->cells, g_random_int_range (0, self->cells->len));
|
|
} while (cell->is_mine);
|
|
|
|
cell->is_mine = TRUE;
|
|
}
|
|
}
|
|
|
|
static SweeperCell *
|
|
get_cell (SweeperGame *self,
|
|
guint x,
|
|
guint y)
|
|
{
|
|
return g_ptr_array_index (self->cells, y * self->width + x);
|
|
}
|
|
|
|
static void
|
|
sweeper_game_count_neighbor_mines (SweeperGame *self,
|
|
guint width,
|
|
guint height)
|
|
{
|
|
guint x, y, x2, y2;
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
SweeperCell *cell = get_cell (self, x, y);
|
|
|
|
for (y2 = MAX (1, y) - 1; y2 < MIN (height, y + 2); y2++)
|
|
{
|
|
for (x2 = MAX (1, x) - 1; x2 < MIN (width, x + 2); x2++)
|
|
{
|
|
SweeperCell *other = get_cell (self, x2, y2);
|
|
|
|
if (other->is_mine)
|
|
cell->neighbor_mines++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
sweeper_game_new_game (SweeperGame *self,
|
|
guint width,
|
|
guint height,
|
|
guint n_mines)
|
|
{
|
|
guint n_items_before;
|
|
|
|
g_return_if_fail (n_mines <= width * height);
|
|
|
|
n_items_before = self->width * self->height;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
sweeper_game_reset_board (self, width, height);
|
|
sweeper_game_place_mines (self, n_mines);
|
|
sweeper_game_count_neighbor_mines (self, width, height);
|
|
|
|
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items_before, width * height);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
|
|
static void
|
|
sweeper_game_init (SweeperGame *self)
|
|
{
|
|
self->cells = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
sweeper_game_new_game (self, 8, 8, 10);
|
|
}
|
|
|
|
static void
|
|
sweeper_game_end (SweeperGame *self,
|
|
gboolean win)
|
|
{
|
|
if (self->playing)
|
|
{
|
|
self->playing = FALSE;
|
|
g_object_notify_by_pspec (G_OBJECT (self), game_properties[GAME_PROP_PLAYING]);
|
|
}
|
|
if (self->win != win)
|
|
{
|
|
self->win = win;
|
|
g_object_notify_by_pspec (G_OBJECT (self), game_properties[GAME_PROP_WIN]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sweeper_game_check_finished (SweeperGame *self)
|
|
{
|
|
guint i;
|
|
|
|
if (!self->playing)
|
|
return;
|
|
|
|
for (i = 0; i < self->cells->len; i++)
|
|
{
|
|
SweeperCell *cell = g_ptr_array_index (self->cells, i);
|
|
|
|
/* There's still a non-revealed cell that isn't a mine */
|
|
if (!cell->is_visible && !cell->is_mine)
|
|
return;
|
|
}
|
|
|
|
sweeper_game_end (self, TRUE);
|
|
}
|
|
|
|
static void
|
|
sweeper_game_reveal_cell (SweeperGame *self,
|
|
guint position)
|
|
{
|
|
SweeperCell *cell;
|
|
|
|
if (!self->playing)
|
|
return;
|
|
|
|
cell = g_ptr_array_index (self->cells, position);
|
|
sweeper_cell_reveal (cell);
|
|
|
|
if (cell->is_mine)
|
|
sweeper_game_end (self, FALSE);
|
|
|
|
sweeper_game_check_finished (self);
|
|
}
|
|
|
|
G_MODULE_EXPORT void
|
|
minesweeper_cell_clicked_cb (GtkGridView *gridview,
|
|
guint pos,
|
|
SweeperGame *game)
|
|
{
|
|
sweeper_game_reveal_cell (game, pos);
|
|
}
|
|
|
|
G_MODULE_EXPORT void
|
|
minesweeper_new_game_cb (GtkButton *button,
|
|
SweeperGame *game)
|
|
{
|
|
sweeper_game_new_game (game, 8, 8, 10);
|
|
}
|
|
|
|
static GtkWidget *window = NULL;
|
|
|
|
GtkWidget *
|
|
do_listview_minesweeper (GtkWidget *do_widget)
|
|
{
|
|
if (window == NULL)
|
|
{
|
|
GtkBuilder *builder;
|
|
|
|
g_type_ensure (SWEEPER_TYPE_GAME);
|
|
|
|
builder = gtk_builder_new_from_resource ("/listview_minesweeper/listview_minesweeper.ui");
|
|
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
|
|
gtk_window_set_display (GTK_WINDOW (window),
|
|
gtk_widget_get_display (do_widget));
|
|
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
|
|
|
|
g_object_unref (builder);
|
|
}
|
|
|
|
if (!gtk_widget_get_visible (window))
|
|
gtk_widget_show (window);
|
|
else
|
|
gtk_window_destroy (GTK_WINDOW (window));
|
|
|
|
return window;
|
|
}
|