1/* Sliding Puzzle
2 * #Keywords: GdkPaintable, GdkGesture, GtkShortcutController, game
3 *
4 * This demo demonstrates how to use gestures and paintables to create a
5 * small sliding puzzle game.
6 */
7
8#include <gtk/gtk.h>
9
10/* Include the header for the puzzle piece */
11#include "puzzlepiece.h"
12#include "paintable.h"
13
14
15static GtkWidget *window = NULL;
16static GtkWidget *frame = NULL;
17static GtkWidget *choices = NULL;
18static GtkWidget *size_spin = NULL;
19static GdkPaintable *puzzle = NULL;
20
21static gboolean solved = TRUE;
22static guint width = 3;
23static guint height = 3;
24static guint pos_x;
25static guint pos_y;
26
27static gboolean
28move_puzzle (GtkWidget *grid,
29 int dx,
30 int dy)
31{
32 GtkWidget *pos, *next;
33 GdkPaintable *piece;
34 guint next_x, next_y;
35
36 /* We don't move anything if the puzzle is solved */
37 if (solved)
38 return FALSE;
39
40 /* Return FALSE if we can't move to where the call
41 * wants us to move.
42 */
43 if ((dx < 0 && pos_x < -dx) ||
44 dx + pos_x >= width ||
45 (dy < 0 && pos_y < -dy) ||
46 dy + pos_y >= height)
47 return FALSE;
48
49 /* Compute the new position */
50 next_x = pos_x + dx;
51 next_y = pos_y + dy;
52
53 /* Get the current and next image */
54 pos = gtk_grid_get_child_at (GTK_GRID (grid), column: pos_x, row: pos_y);
55 next = gtk_grid_get_child_at (GTK_GRID (grid), column: next_x, row: next_y);
56
57 /* Move the displayed piece. */
58 piece = gtk_picture_get_paintable (self: GTK_PICTURE (ptr: next));
59 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: pos), paintable: piece);
60 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: next), NULL);
61
62 /* Update the current position */
63 pos_x = next_x;
64 pos_y = next_y;
65
66 /* Return TRUE because we successfully moved the piece */
67 return TRUE;
68}
69
70static void
71shuffle_puzzle (GtkWidget *grid)
72{
73 guint i, n_steps;
74
75 /* Do this many random moves */
76 n_steps = width * height * 50;
77
78 for (i = 0; i < n_steps; i++)
79 {
80 /* Get a random number for the direction to move in */
81 switch (g_random_int_range (begin: 0, end: 4))
82 {
83 case 0:
84 /* left */
85 move_puzzle (grid, dx: -1, dy: 0);
86 break;
87
88 case 1:
89 /* up */
90 move_puzzle (grid, dx: 0, dy: -1);
91 break;
92
93 case 2:
94 /* right */
95 move_puzzle (grid, dx: 1, dy: 0);
96 break;
97
98 case 3:
99 /* down */
100 move_puzzle (grid, dx: 0, dy: 1);
101 break;
102
103 default:
104 g_assert_not_reached ();
105 continue;
106 }
107 }
108}
109
110static gboolean
111check_solved (GtkWidget *grid)
112{
113 GtkWidget *picture;
114 GdkPaintable *piece;
115 guint x, y;
116
117 /* Nothing to check if the puzzle is already solved */
118 if (solved)
119 return TRUE;
120
121 /* If the empty cell isn't in the bottom right,
122 * the puzzle is obviously not solved */
123 if (pos_x != width - 1 ||
124 pos_y != height - 1)
125 return FALSE;
126
127 /* Check that all pieces are in the right position */
128 for (y = 0; y < height; y++)
129 {
130 for (x = 0; x < width; x++)
131 {
132 picture = gtk_grid_get_child_at (GTK_GRID (grid), column: x, row: y);
133 piece = gtk_picture_get_paintable (self: GTK_PICTURE (ptr: picture));
134
135 /* empty cell */
136 if (piece == NULL)
137 continue;
138
139 if (gtk_puzzle_piece_get_x (self: GTK_PUZZLE_PIECE (ptr: piece)) != x ||
140 gtk_puzzle_piece_get_y (self: GTK_PUZZLE_PIECE (ptr: piece)) != y)
141 return FALSE;
142 }
143 }
144
145 /* We solved the puzzle!
146 */
147 solved = TRUE;
148
149 /* Fill the empty cell to show that we're done.
150 */
151 picture = gtk_grid_get_child_at (GTK_GRID (grid), column: 0, row: 0);
152 piece = gtk_picture_get_paintable (self: GTK_PICTURE (ptr: picture));
153
154 piece = gtk_puzzle_piece_new (puzzle: gtk_puzzle_piece_get_puzzle (self: GTK_PUZZLE_PIECE (ptr: piece)),
155 x: pos_x, y: pos_y,
156 width, height);
157 picture = gtk_grid_get_child_at (GTK_GRID (grid), column: pos_x, row: pos_y);
158 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: picture), paintable: piece);
159
160 return TRUE;
161}
162
163static gboolean
164puzzle_key_pressed (GtkWidget *grid,
165 GVariant *args,
166 gpointer unused)
167{
168 int dx, dy;
169
170 g_variant_get (value: args, format_string: "(ii)", &dx, &dy);
171
172 if (!move_puzzle (grid, dx, dy))
173 {
174 /* Make the error sound and then return TRUE.
175 * We handled this key, even though we didn't
176 * do anything to the puzzle.
177 */
178 gtk_widget_error_bell (widget: grid);
179 return TRUE;
180 }
181
182 check_solved (grid);
183
184 return TRUE;
185}
186
187static void
188puzzle_button_pressed (GtkGestureClick *gesture,
189 int n_press,
190 double x,
191 double y,
192 GtkWidget *grid)
193{
194 GtkWidget *child;
195 int l, t, i;
196 int pos;
197
198 child = gtk_widget_pick (widget: grid, x, y, flags: GTK_PICK_DEFAULT);
199
200 if (!child)
201 {
202 gtk_widget_error_bell (widget: grid);
203 return;
204 }
205
206 gtk_grid_query_child (GTK_GRID (grid), child, column: &l, row: &t, NULL, NULL);
207
208 if (l == pos_x && t == pos_y)
209 {
210 gtk_widget_error_bell (widget: grid);
211 }
212 else if (l == pos_x)
213 {
214 pos = pos_y;
215 for (i = t; i < pos; i++)
216 {
217 if (!move_puzzle (grid, dx: 0, dy: -1))
218 gtk_widget_error_bell (widget: grid);
219 }
220 for (i = pos; i < t; i++)
221 {
222 if (!move_puzzle (grid, dx: 0, dy: 1))
223 gtk_widget_error_bell (widget: grid);
224 }
225 }
226 else if (t == pos_y)
227 {
228 pos = pos_x;
229 for (i = l; i < pos; i++)
230 {
231 if (!move_puzzle (grid, dx: -1, dy: 0))
232 gtk_widget_error_bell (widget: grid);
233 }
234 for (i = pos; i < l; i++)
235 {
236 if (!move_puzzle (grid, dx: 1, dy: 0))
237 gtk_widget_error_bell (widget: grid);
238 }
239 }
240 else
241 {
242 gtk_widget_error_bell (widget: grid);
243 }
244
245 check_solved (grid);
246}
247
248static void
249add_move_binding (GtkShortcutController *controller,
250 guint keyval,
251 guint kp_keyval,
252 int dx,
253 int dy)
254{
255 GtkShortcut *shortcut;
256
257 shortcut = gtk_shortcut_new_with_arguments (
258 trigger: gtk_alternative_trigger_new (first: gtk_keyval_trigger_new (keyval, modifiers: 0),
259 second: gtk_keyval_trigger_new (keyval: kp_keyval, modifiers: 0)),
260 action: gtk_callback_action_new (callback: puzzle_key_pressed, NULL, NULL),
261 format_string: "(ii)", dx, dy);
262 gtk_shortcut_controller_add_shortcut (self: controller, shortcut);
263}
264
265static void
266start_puzzle (GdkPaintable *paintable)
267{
268 GtkWidget *picture, *grid;
269 GtkEventController *controller;
270 guint x, y;
271 float aspect_ratio;
272
273 /* Create a new grid */
274 grid = gtk_grid_new ();
275 gtk_widget_set_focusable (widget: grid, TRUE);
276 gtk_aspect_frame_set_child (GTK_ASPECT_FRAME (frame), child: grid);
277 aspect_ratio = gdk_paintable_get_intrinsic_aspect_ratio (paintable);
278 if (aspect_ratio == 0.0)
279 aspect_ratio = 1.0;
280 gtk_aspect_frame_set_ratio (GTK_ASPECT_FRAME (frame), ratio: aspect_ratio);
281 gtk_aspect_frame_set_obey_child (GTK_ASPECT_FRAME (frame), FALSE);
282
283 /* Add shortcuts so people can use the arrow
284 * keys to move the puzzle
285 */
286 controller = gtk_shortcut_controller_new ();
287 gtk_shortcut_controller_set_scope (GTK_SHORTCUT_CONTROLLER (controller),
288 scope: GTK_SHORTCUT_SCOPE_LOCAL);
289 add_move_binding (GTK_SHORTCUT_CONTROLLER (controller),
290 GDK_KEY_Left, GDK_KEY_KP_Left,
291 dx: -1, dy: 0);
292 add_move_binding (GTK_SHORTCUT_CONTROLLER (controller),
293 GDK_KEY_Right, GDK_KEY_KP_Right,
294 dx: 1, dy: 0);
295 add_move_binding (GTK_SHORTCUT_CONTROLLER (controller),
296 GDK_KEY_Up, GDK_KEY_KP_Up,
297 dx: 0, dy: -1);
298 add_move_binding (GTK_SHORTCUT_CONTROLLER (controller),
299 GDK_KEY_Down, GDK_KEY_KP_Down,
300 dx: 0, dy: 1);
301 gtk_widget_add_controller (GTK_WIDGET (grid), controller);
302
303 controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
304 g_signal_connect (controller, "pressed",
305 G_CALLBACK (puzzle_button_pressed),
306 grid);
307 gtk_widget_add_controller (GTK_WIDGET (grid), controller);
308
309 /* Make sure the cells have equal size */
310 gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE);
311 gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
312
313 /* Reset the variables */
314 solved = FALSE;
315 pos_x = width - 1;
316 pos_y = height - 1;
317
318 /* add a picture for every cell */
319 for (y = 0; y < height; y++)
320 {
321 for (x = 0; x < width; x++)
322 {
323 GdkPaintable *piece;
324
325 /* Don't paint anything for the lsiding part of the video */
326 if (x == pos_x && y == pos_y)
327 piece = NULL;
328 else
329 piece = gtk_puzzle_piece_new (puzzle: paintable,
330 x, y,
331 width, height);
332 picture = gtk_picture_new_for_paintable (paintable: piece);
333 gtk_picture_set_keep_aspect_ratio (self: GTK_PICTURE (ptr: picture), FALSE);
334 gtk_grid_attach (GTK_GRID (grid),
335 child: picture,
336 column: x, row: y,
337 width: 1, height: 1);
338 }
339 }
340
341 shuffle_puzzle (grid);
342}
343
344static void
345reshuffle (void)
346{
347 GtkWidget *grid;
348
349 if (solved)
350 {
351 start_puzzle (paintable: puzzle);
352 grid = gtk_aspect_frame_get_child (GTK_ASPECT_FRAME (frame));
353 }
354 else
355 {
356 grid = gtk_aspect_frame_get_child (GTK_ASPECT_FRAME (frame));
357 shuffle_puzzle (grid);
358 }
359 gtk_widget_grab_focus (widget: grid);
360}
361
362static void
363reconfigure (void)
364{
365 GtkWidget *popover;
366 GtkWidget *grid;
367 GtkWidget *child;
368 GtkWidget *image;
369 GList *selected;
370
371 width = height = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (size_spin));
372
373 selected = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (choices));
374 if (selected == NULL)
375 child = gtk_widget_get_first_child (widget: choices);
376 else
377 {
378 child = selected->data;
379 g_list_free (list: selected);
380 }
381
382 image = gtk_flow_box_child_get_child (GTK_FLOW_BOX_CHILD (child));
383 puzzle = gtk_image_get_paintable (GTK_IMAGE (image));
384
385 start_puzzle (paintable: puzzle);
386 popover = gtk_widget_get_ancestor (widget: size_spin, GTK_TYPE_POPOVER);
387 gtk_popover_popdown (GTK_POPOVER (popover));
388 grid = gtk_aspect_frame_get_child (GTK_ASPECT_FRAME (frame));
389 gtk_widget_grab_focus (widget: grid);
390}
391
392static void
393add_choice (GtkWidget *container,
394 GdkPaintable *paintable)
395{
396 GtkWidget *icon;
397
398 icon = gtk_image_new_from_paintable (paintable);
399 gtk_image_set_icon_size (GTK_IMAGE (icon), icon_size: GTK_ICON_SIZE_LARGE);
400
401 gtk_flow_box_insert (GTK_FLOW_BOX (container), widget: icon, position: -1);
402}
403
404static void
405widget_destroyed (gpointer data,
406 GObject *widget)
407{
408 if (data)
409 *(gpointer *) data = NULL;
410}
411
412
413GtkWidget *
414do_sliding_puzzle (GtkWidget *do_widget)
415{
416 if (!window)
417 {
418 GtkWidget *header;
419 GtkWidget *restart;
420 GtkWidget *tweak;
421 GtkWidget *popover;
422 GtkWidget *tweaks;
423 GtkWidget *apply;
424 GtkWidget *label;
425 GtkWidget *sw;
426 GtkMediaStream *media;
427
428 puzzle = GDK_PAINTABLE (ptr: gdk_texture_new_from_resource (resource_path: "/sliding_puzzle/portland-rose.jpg"));
429
430 tweaks = gtk_grid_new ();
431 gtk_grid_set_row_spacing (GTK_GRID (tweaks), spacing: 10);
432 gtk_grid_set_column_spacing (GTK_GRID (tweaks), spacing: 10);
433 gtk_widget_set_margin_start (widget: tweaks, margin: 10);
434 gtk_widget_set_margin_end (widget: tweaks, margin: 10);
435 gtk_widget_set_margin_top (widget: tweaks, margin: 10);
436 gtk_widget_set_margin_bottom (widget: tweaks, margin: 10);
437
438 choices = gtk_flow_box_new ();
439 gtk_widget_add_css_class (widget: choices, css_class: "view");
440 add_choice (container: choices, paintable: puzzle);
441 add_choice (container: choices, paintable: gtk_nuclear_animation_new (TRUE));
442 media = gtk_media_file_new_for_resource (resource_path: "/images/gtk-logo.webm");
443 gtk_media_stream_set_loop (self: media, TRUE);
444 gtk_media_stream_set_muted (self: media, TRUE);
445 gtk_media_stream_play (self: media);
446 add_choice (container: choices, paintable: GDK_PAINTABLE (ptr: media));
447 sw = gtk_scrolled_window_new ();
448 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: choices);
449 gtk_grid_attach (GTK_GRID (tweaks), child: sw, column: 0, row: 0, width: 2, height: 1);
450
451 label = gtk_label_new (str: "Size");
452 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
453 gtk_grid_attach (GTK_GRID (tweaks), child: label, column: 0, row: 1, width: 1, height: 1);
454 size_spin = gtk_spin_button_new_with_range (min: 2, max: 10, step: 1);
455 gtk_spin_button_set_value (GTK_SPIN_BUTTON (size_spin), value: width);
456 gtk_grid_attach (GTK_GRID (tweaks), child: size_spin, column: 1, row: 1, width: 1, height: 1);
457
458 apply = gtk_button_new_with_label (label: "Apply");
459 gtk_widget_set_halign (widget: apply, align: GTK_ALIGN_END);
460 gtk_grid_attach (GTK_GRID (tweaks), child: apply, column: 1, row: 2, width: 1, height: 1);
461 g_signal_connect (apply, "clicked", G_CALLBACK (reconfigure), NULL);
462
463 popover = gtk_popover_new ();
464 gtk_popover_set_child (GTK_POPOVER (popover), child: tweaks);
465
466 tweak = gtk_menu_button_new ();
467 gtk_menu_button_set_popover (GTK_MENU_BUTTON (tweak), popover);
468 gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (tweak), icon_name: "emblem-system-symbolic");
469
470 restart = gtk_button_new_from_icon_name (icon_name: "view-refresh-symbolic");
471 g_signal_connect (restart, "clicked", G_CALLBACK (reshuffle), NULL);
472
473 header = gtk_header_bar_new ();
474 gtk_header_bar_pack_start (GTK_HEADER_BAR (header), child: restart);
475 gtk_header_bar_pack_end (GTK_HEADER_BAR (header), child: tweak);
476 window = gtk_window_new ();
477 gtk_window_set_display (GTK_WINDOW (window),
478 display: gtk_widget_get_display (widget: do_widget));
479 gtk_window_set_title (GTK_WINDOW (window), title: "Sliding Puzzle");
480 gtk_window_set_titlebar (GTK_WINDOW (window), titlebar: header);
481 gtk_window_set_default_size (GTK_WINDOW (window), width: 400, height: 300);
482 g_object_weak_ref (G_OBJECT (window), notify: widget_destroyed, data: &window);
483
484 frame = gtk_aspect_frame_new (xalign: 0.5, yalign: 0.5, ratio: (float) gdk_paintable_get_intrinsic_aspect_ratio (paintable: puzzle), FALSE);
485 gtk_window_set_child (GTK_WINDOW (window), child: frame);
486
487 start_puzzle (paintable: puzzle);
488 }
489
490 if (!gtk_widget_get_visible (widget: window))
491 gtk_widget_show (widget: window);
492 else
493 gtk_window_destroy (GTK_WINDOW (window));
494
495 return window;
496}
497

source code of gtk/demos/gtk-demo/sliding_puzzle.c