mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 14:10:30 +00:00
d732c869c2
Add the names of the main widgets as keywords to our demos, but also things like "game". This helps finding relevant demos in our growing list. You can now for example type "label", and find the "error states" and "links" demos showing GtkLabel features.
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);
|
|
}
|
|
|
|
void
|
|
minesweeper_cell_clicked_cb (GtkGridView *gridview,
|
|
guint pos,
|
|
SweeperGame *game)
|
|
{
|
|
sweeper_game_reveal_cell (game, pos);
|
|
}
|
|
|
|
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;
|
|
}
|