1/* Peg Solitaire
2 * #Keywords: GtkGridView, game
3 *
4 * This demo demonstrates how to use drag-and-drop to implement peg solitaire.
5 *
6 */
7
8#include "config.h"
9#include <gtk/gtk.h>
10
11/* Create an object for the pegs that get moved around in the game.
12 *
13 * We implement the GdkPaintable interface for them, so we can use GtkPicture
14 * objects for the wholes we put the pegs into.
15 */
16#define SOLITAIRE_TYPE_PEG (solitaire_peg_get_type ())
17G_DECLARE_FINAL_TYPE (SolitairePeg, solitaire_peg, SOLITAIRE, PEG, GObject)
18
19/* Declare the struct. */
20struct _SolitairePeg
21{
22 GObject parent_instance;
23
24 int x;
25 int y;
26};
27
28struct _SolitairePegClass
29{
30 GObjectClass parent_class;
31};
32
33/* Here, we implement the functionality required by the GdkPaintable interface */
34static void
35solitaire_peg_snapshot (GdkPaintable *paintable,
36 GdkSnapshot *snapshot,
37 double width,
38 double height)
39{
40 /* The snapshot function is the only function we need to implement.
41 * It does the actual drawing of the paintable.
42 */
43 gtk_snapshot_append_color (snapshot,
44 color: &(GdkRGBA) { 0.6, 0.3, 0.0, 1.0 },
45 bounds: &GRAPHENE_RECT_INIT (0, 0, width, height));
46}
47
48static GdkPaintableFlags
49solitaire_peg_get_flags (GdkPaintable *paintable)
50{
51 /* The flags are very useful to let GTK know that this image
52 * is never going to change.
53 * This allows many optimizations and should therefore always
54 * be set.
55 */
56 return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
57}
58
59static int
60solitaire_peg_get_intrinsic_width (GdkPaintable *paintable)
61{
62 return 32;
63}
64
65static int
66solitaire_peg_get_intrinsic_height (GdkPaintable *paintable)
67{
68 return 32;
69}
70
71static void
72solitaire_peg_paintable_init (GdkPaintableInterface *iface)
73{
74 iface->snapshot = solitaire_peg_snapshot;
75 iface->get_flags = solitaire_peg_get_flags;
76 iface->get_intrinsic_width = solitaire_peg_get_intrinsic_width;
77 iface->get_intrinsic_height = solitaire_peg_get_intrinsic_height;
78}
79
80/* When defining the GType, we need to implement the GdkPaintable interface */
81G_DEFINE_TYPE_WITH_CODE (SolitairePeg, solitaire_peg, G_TYPE_OBJECT,
82 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
83 solitaire_peg_paintable_init))
84
85/* Here's the boilerplate for the GObject declaration.
86 * We don't need to do anything special here, because we keep no
87 * data of our own.
88 */
89static void
90solitaire_peg_class_init (SolitairePegClass *klass)
91{
92}
93
94static void
95solitaire_peg_init (SolitairePeg *peg)
96{
97}
98
99/* Add a little setter for the peg's position.
100 * We want to track those so that we can check for legal moves
101 * during drag-and-drop operations.
102 */
103static void
104solitaire_peg_set_position (SolitairePeg *peg,
105 guint x,
106 guint y)
107{
108 peg->x = x;
109 peg->y = y;
110}
111
112/* And finally, we add a simple constructor.
113 */
114static SolitairePeg *
115solitaire_peg_new (void)
116{
117 return g_object_new (SOLITAIRE_TYPE_PEG, NULL);
118}
119
120/*** Helper for finding a win ***/
121
122static void
123ended (GObject *object)
124{
125 g_object_unref (object);
126}
127
128static void
129celebrate (gboolean win)
130{
131 char *path;
132 GtkMediaStream *stream;
133
134 if (win)
135 path = g_build_filename (GTK_DATADIR, "sounds", "freedesktop", "stereo", "complete.oga", NULL);
136 else
137 path = g_build_filename (GTK_DATADIR, "sounds", "freedesktop", "stereo", "dialog-error.oga", NULL);
138 stream = gtk_media_file_new_for_filename (filename: path);
139 gtk_media_stream_set_volume (self: stream, volume: 1.0);
140 gtk_media_stream_play (self: stream);
141
142 g_signal_connect (stream, "notify::ended", G_CALLBACK (ended), NULL);
143 g_free (mem: path);
144}
145
146static int
147check_move (GtkGrid *grid,
148 int x,
149 int y,
150 int dx,
151 int dy)
152{
153 GtkWidget *image;
154 /* We have a peg at x, y.
155 * Check if we can move the peg to x + 2*dx, y + 2*dy
156 */
157 image = gtk_grid_get_child_at (grid, column: x + dx, row: y + dy);
158 if (!GTK_IS_IMAGE (image) ||
159 !SOLITAIRE_IS_PEG (ptr: gtk_image_get_paintable (GTK_IMAGE (image))))
160 return 0;
161
162 image = gtk_grid_get_child_at (grid, column: x + 2*dx, row: y + 2*dy);
163 if (!GTK_IMAGE (image) ||
164 SOLITAIRE_IS_PEG (ptr: gtk_image_get_paintable (GTK_IMAGE (image))))
165 return 0;
166
167 return 1;
168}
169
170static void
171check_for_end (GtkGrid *grid)
172{
173 GtkWidget *image;
174 int x, y;
175 int pegs;
176 int moves;
177
178 pegs = 0;
179 moves = 0;
180 for (x = 0; x < 7; x++)
181 {
182 for (y = 0; y < 7; y++)
183 {
184 image = gtk_grid_get_child_at (grid, column: x, row: y);
185 if (GTK_IS_IMAGE (image) &&
186 SOLITAIRE_IS_PEG (ptr: gtk_image_get_paintable (GTK_IMAGE (image))))
187 {
188 pegs++;
189 moves += check_move (grid, x, y, dx: 1, dy: 0);
190 moves += check_move (grid, x, y, dx: -1, dy: 0);
191 moves += check_move (grid, x, y, dx: 0, dy: 1);
192 moves += check_move (grid, x, y, dx: 0, dy: -1);
193 }
194
195 if (pegs > 1 && moves > 0)
196 break;
197 }
198 }
199
200 image = gtk_grid_get_child_at (grid, column: 3, row: 3);
201 if (pegs == 1 &&
202 SOLITAIRE_IS_PEG (ptr: gtk_image_get_paintable (GTK_IMAGE (image))))
203 celebrate (TRUE);
204 else if (moves == 0)
205 celebrate (FALSE);
206}
207
208
209/*** DRAG AND DROP ***/
210
211/* The user tries to start a drag operation.
212 * We check if the image contains a peg, and if so, we return the
213 * peg as the content to be dragged.
214 */
215static GdkContentProvider *
216drag_prepare (GtkDragSource *source,
217 double x,
218 double y,
219 GtkWidget *image)
220{
221 GdkPaintable *paintable = gtk_image_get_paintable (GTK_IMAGE (image));
222
223 if (!SOLITAIRE_IS_PEG (ptr: paintable))
224 return NULL;
225
226 return gdk_content_provider_new_typed (SOLITAIRE_TYPE_PEG, paintable);
227}
228
229/* This notifies us that the drag has begun.
230 * We can now set up the icon and the widget for the ongoing drag.
231 */
232static void
233drag_begin (GtkDragSource *source,
234 GdkDrag *drag,
235 GtkWidget *image)
236{
237 GdkPaintable *paintable = gtk_image_get_paintable (GTK_IMAGE (image));
238
239 /* We guaranteed in the drag_prepare function above that we
240 * only start a drag if a peg is available.
241 * So let's make sure we did not screw that up.
242 */
243 g_assert (SOLITAIRE_IS_PEG (paintable));
244
245 /* We use the peg as the drag icon.
246 */
247 gtk_drag_source_set_icon (source, paintable, hot_x: -2, hot_y: -2);
248
249 /* We also attach it to the drag operation as custom user data,
250 * so that we can get it back later if the drag fails.
251 */
252 g_object_set_data (G_OBJECT (drag), key: "the peg", data: paintable);
253
254 /* Because we are busy dragging the peg, we want to unset it
255 * on the image.
256 */
257 gtk_image_clear (GTK_IMAGE (image));
258}
259
260/* This is called once a drag operation has ended (successfully or not).
261 * We want to undo what we did in drag_begin() above and react
262 * to a potential move of the peg.
263 */
264static void
265drag_end (GtkDragSource *source,
266 GdkDrag *drag,
267 gboolean delete_data,
268 GtkWidget *image)
269{
270 SolitairePeg *peg;
271
272 /* If the drag was successful, we should now delete the peg.
273 * We did this in drag_begin() above to prepare for the drag, so
274 * there's no need to do anything anymore.
275 */
276 if (delete_data)
277 return;
278
279 /* However, if the drag did not succeed, we need to undo what
280 * we did in drag_begin() and reinsert the peg here.
281 * Because we used it as the drag data
282 */
283 peg = g_object_get_data (G_OBJECT (drag), key: "the peg");
284 gtk_image_set_from_paintable (GTK_IMAGE (image), paintable: GDK_PAINTABLE (ptr: peg));
285}
286
287/* Whenever a new drop operation starts, we need to check if we can
288 * accept it.
289 * The default check unfortunately is not good enough, because it only
290 * checks the data type. But we also need to check if our image can
291 * even accept data.
292 */
293static gboolean
294drop_accept (GtkDropTarget *target,
295 GdkDrop *drop,
296 GtkWidget *image)
297{
298 /* First, check the drop is actually trying to drop a peg */
299 if (!gdk_content_formats_contain_gtype (formats: gdk_drop_get_formats (self: drop), SOLITAIRE_TYPE_PEG))
300 return FALSE;
301
302 /* If the image already contains a peg, we cannot accept another one */
303 if (SOLITAIRE_IS_PEG (ptr: gtk_image_get_paintable (GTK_IMAGE (image))))
304 return FALSE;
305
306 return TRUE;
307}
308
309static gboolean
310drop_drop (GtkDropTarget *target,
311 const GValue *value,
312 double x,
313 double y,
314 GtkWidget *image)
315{
316 GtkGrid *grid;
317 SolitairePeg *peg;
318 int image_x, image_y;
319 GtkWidget *jumped;
320
321 grid = GTK_GRID (gtk_widget_get_parent (image));
322 /* The value contains the data in the type we demanded.
323 * We demanded a SolitairePeg, so that's what we get.
324 */
325 peg = g_value_get_object (value);
326
327 /* Make sure this was a legal move. */
328 /* First, figure out the image's position in the grid. */
329 gtk_grid_query_child (grid,
330 child: image,
331 column: &image_x, row: &image_y,
332 NULL, NULL);
333
334 /* If the peg was not moved 2 spaces horizontally or vertically,
335 * this was not a valid jump. Reject it.
336 */
337 if (!((ABS (image_x - peg->x) == 2 && image_y == peg->y) ||
338 (ABS (image_y - peg->y) == 2 && image_x == peg->x)))
339 return FALSE;
340
341 /* Get the widget that was jumped over
342 */
343 jumped = gtk_grid_get_child_at (grid,
344 column: (image_x + peg->x) / 2,
345 row: (image_y + peg->y) / 2);
346 /* If the jumped widget does not have a peg in it, this move
347 * isn't valid.
348 */
349 if (!SOLITAIRE_IS_PEG (ptr: gtk_image_get_paintable (GTK_IMAGE (jumped))))
350 return FALSE;
351
352 /* Finally, we know it's a legal move. */
353
354 /* Clear the peg of the jumped-over image */
355 gtk_image_clear (GTK_IMAGE (jumped));
356
357 /* Add the peg to this image */
358 solitaire_peg_set_position (peg, x: image_x, y: image_y);
359 gtk_image_set_from_paintable (GTK_IMAGE (image), paintable: GDK_PAINTABLE (ptr: peg));
360
361 /* Maybe we have something to celebrate */
362 check_for_end (grid);
363
364 /* Success! */
365 return TRUE;
366}
367
368static void
369create_board (GtkWidget *window)
370{
371 GtkWidget *grid;
372 GtkWidget *image;
373 int x, y;
374 GtkDragSource *source;
375 GtkDropTarget *target;
376 GtkCssProvider *provider;
377 const char css[] =
378 ".solitaire-field {"
379 " border: 1px solid lightgray;"
380 "}";
381
382 provider = gtk_css_provider_new ();
383 gtk_css_provider_load_from_data (css_provider: provider, data: css, length: -1);
384
385 grid = gtk_grid_new ();
386 gtk_widget_set_halign (widget: grid, align: GTK_ALIGN_CENTER);
387 gtk_widget_set_valign (widget: grid, align: GTK_ALIGN_CENTER);
388 gtk_grid_set_row_spacing (GTK_GRID (grid), spacing: 6);
389 gtk_grid_set_column_spacing (GTK_GRID (grid), spacing: 6);
390 gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE);
391 gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
392 gtk_window_set_child (GTK_WINDOW (window), child: grid);
393
394 for (x = 0; x < 7; x++)
395 {
396 for (y = 0; y < 7; y++)
397 {
398 if ((x < 2 || x >= 5) && (y < 2 || y >= 5))
399 continue;
400
401 image = gtk_image_new ();
402 gtk_style_context_add_provider (context: gtk_widget_get_style_context (widget: image),
403 GTK_STYLE_PROVIDER (provider),
404 priority: 800);
405 gtk_widget_add_css_class (widget: image, css_class: "solitaire-field");
406 gtk_image_set_icon_size (GTK_IMAGE (image), icon_size: GTK_ICON_SIZE_LARGE);
407 if (x != 3 || y != 3)
408 {
409 SolitairePeg *peg = solitaire_peg_new ();
410 solitaire_peg_set_position (peg, x, y);
411 gtk_image_set_from_paintable (GTK_IMAGE (image), paintable: GDK_PAINTABLE (ptr: peg));
412 }
413
414 gtk_grid_attach (GTK_GRID (grid), child: image, column: x, row: y, width: 1, height: 1);
415
416 /* Set up the drag source.
417 * This is rather straightforward: Set the supported actions
418 * (in our case, pegs can only be moved) and connect all the
419 * relevant signals.
420 * And because all drag'n'drop handling is done via event controllers,
421 * we need to add the controller to the widget.
422 */
423 source = gtk_drag_source_new ();
424 gtk_drag_source_set_actions (source, actions: GDK_ACTION_MOVE);
425 g_signal_connect (source, "prepare", G_CALLBACK (drag_prepare), image);
426 g_signal_connect (source, "drag-begin", G_CALLBACK (drag_begin), image);
427 g_signal_connect (source, "drag-end", G_CALLBACK (drag_end), image);
428 gtk_widget_add_controller (widget: image, GTK_EVENT_CONTROLLER (source));
429
430 /* Set up the drop target.
431 * This is more involved, because the game logic goes here.
432 */
433
434 /* First we specify the data we accept: pegs.
435 * And we only want moves.
436 */
437 target = gtk_drop_target_new (SOLITAIRE_TYPE_PEG, actions: GDK_ACTION_MOVE);
438 /* Then we connect our signals.
439 */
440 g_signal_connect (target, "accept", G_CALLBACK (drop_accept), image);
441 g_signal_connect (target, "drop", G_CALLBACK (drop_drop), image);
442 /* Finally, like above, we add it to the widget.
443 */
444 gtk_widget_add_controller (widget: image, GTK_EVENT_CONTROLLER (target));
445 }
446 }
447
448 g_object_unref (object: provider);
449}
450
451static void
452restart_game (GtkButton *button,
453 GtkWidget *window)
454{
455 create_board (window);
456}
457
458GtkWidget *
459do_peg_solitaire (GtkWidget *do_widget)
460{
461 static GtkWidget *window = NULL;
462
463 if (!window)
464 {
465 GtkWidget *header;
466 GtkWidget *restart;
467
468 window = gtk_window_new ();
469
470 restart = gtk_button_new_from_icon_name (icon_name: "view-refresh-symbolic");
471 g_signal_connect (restart, "clicked", G_CALLBACK (restart_game), window);
472
473 header = gtk_header_bar_new ();
474 gtk_header_bar_pack_start (GTK_HEADER_BAR (header), child: restart);
475 gtk_window_set_display (GTK_WINDOW (window),
476 display: gtk_widget_get_display (widget: do_widget));
477 gtk_window_set_title (GTK_WINDOW (window), title: "Peg Solitaire");
478 gtk_window_set_titlebar (GTK_WINDOW (window), titlebar: header);
479 gtk_window_set_default_size (GTK_WINDOW (window), width: 400, height: 300);
480 g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window);
481
482 create_board (window);
483 }
484
485 if (!gtk_widget_get_visible (widget: window))
486 gtk_widget_show (widget: window);
487 else
488 gtk_window_destroy (GTK_WINDOW (window));
489
490 return window;
491}
492

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