Got gameplay working and board resizing with window.
[gmemory:gmemory.git] / src / gmemory-game.c
1 /*
2  *  GMemory - gmemory-game.c
3  *
4  *  Copyright (C) 2010 Openismus GmbH
5  *
6  *  Author: Chris Kühl <chrisk@openismus.com>
7  *
8  *  This program is free software: you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation, either version 3 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #include "gmemory-game.h"
23 #include "gmemory-card.h"
24 #include <limits.h>
25 #include <string.h>
26
27 /* Prototypes. */
28 static void set_game_state(GMemoryGame *game, GMemoryGameState state);
29 static void hide_unmatched_card_pairs(GMemoryCardPair * card_pair, GMemoryGame * game);
30 static void hide_unmatched_cards(GMemoryGame *game);
31 static gboolean check_for_match(GMemoryGame *game, GMemoryCard *card1, GMemoryCard *card2);
32 static GList * get_randomized_positions(GMemoryGame *game);
33 static void layout_cards(GMemoryGame *game, gboolean intial_layout);
34 static void clear_card_pairs(GMemoryGame *game);
35 static void initialize_cardpairs(GMemoryGame *game);
36 static void initialize_new_game(GMemoryGame *game);
37
38 static gboolean on_card_clicked(GMemoryCard *card, ClutterEvent *event, gpointer data);
39
40 G_DEFINE_TYPE(GMemoryGame, gmemory_game, CLUTTER_TYPE_GROUP);
41
42 enum {
43         PROP_0,
44
45         PROP_DIFFICULTY,
46         PROP_ELAPSED_TIME,
47         PROP_ATTEMPTS,
48         PROP_MATCHES,
49         PROP_STATE
50 };
51
52 enum {
53         START_GAME,
54         END_GAME,
55         ATTEMPT_MADE,
56
57         LAST_SIGNAL
58 };
59 static guint signals[LAST_SIGNAL];
60
61 #define GMEMORY_GAME_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GMEMORY_TYPE_GAME, GMemoryGamePrivate))
62
63 struct _GMemoryGamePrivate {
64         ClutterActorBox   allocation;
65         guint             difficulty;
66         GList            *card_pairs;
67         /* Pointer to the card that was selected first in an attempted match. */
68         GMemoryCard      *selected_card;
69         /* Current position assignment: A pointer to card in the ith position.*/
70         GPtrArray        *curr_pos_assign;
71         GPtrArray        *matched_pairs;
72         GMemoryGameState  state;
73         GTimer           *timer;
74         guint             attempts;
75         guint             matches;
76 };
77
78 static const guint min_difficulty     = GAME_DIFFICULTY_EASY;
79 static const guint max_difficulty     = GAME_DIFFICULTY_EXTREME;
80 static const guint default_difficulty = GAME_DIFFICULTY_EASY;
81
82 static const guint CARD_SPACING  = 10;
83 static const guint CARD_SHUFFLES = 100;
84
85 #define MAX_MATCHES ((guint)(max_difficulty*max_difficulty)/2) /* Truncate if odd.*/
86
87 /* Property accessor functions. */
88 static void
89 gmemory_game_set_property(GObject      *object, guint       prop_id,
90                           const GValue *value,  GParamSpec *pspec) {
91         GMemoryGame *game = GMEMORY_GAME(object);
92
93         switch (prop_id) {
94         case PROP_DIFFICULTY:
95                 gmemory_game_set_difficulty(game, g_value_get_uint(value));
96                 break;
97         default:
98                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
99                 break;
100         }
101 }
102
103 static void
104 gmemory_game_get_property (GObject *object, guint       prop_id,
105                            GValue  *value,  GParamSpec *pspec) {
106         GMemoryGamePrivate *priv = GMEMORY_GAME(object)->priv;
107
108         switch (prop_id) {
109         case PROP_DIFFICULTY:
110                 g_value_set_uint(value, priv->difficulty);
111                 break;
112         case PROP_ELAPSED_TIME:
113         {
114                 gulong elapsed_time_in_us;
115                 g_timer_elapsed(priv->timer, &elapsed_time_in_us);
116                 g_value_set_ulong(value, elapsed_time_in_us);
117                 break;
118         }
119         case PROP_ATTEMPTS:
120                 g_value_set_uint(value, priv->attempts);
121                 break;
122         case PROP_MATCHES:
123                 g_value_set_uint(value, priv->matches);
124                 break;
125         default:
126                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
127                 break;
128         }
129 }
130
131 /* Virtual function overrides. */
132 static void
133 gmemory_game_dispose(GObject *gobject) {
134         GMemoryGame *self = GMEMORY_GAME(gobject);
135         GMemoryGamePrivate *priv = GMEMORY_GAME_GET_PRIVATE(self);
136
137         if(priv->curr_pos_assign)
138                 priv->curr_pos_assign = g_ptr_array_free(priv->curr_pos_assign, TRUE);
139
140         if(priv->matched_pairs)
141                 priv->matched_pairs = g_ptr_array_free(priv->matched_pairs, TRUE);
142
143         clear_card_pairs(self);
144
145         /* Chain up to the parent class */
146         G_OBJECT_CLASS(gmemory_game_parent_class)->dispose(gobject);
147 }
148
149 static void
150 gmemory_game_finalize(GObject *gobject) {
151         GMemoryGame *self = GMEMORY_GAME(gobject);
152
153         g_timer_destroy(self->priv->timer);
154
155         /* Chain up to the parent class */
156         G_OBJECT_CLASS(gmemory_game_parent_class)->finalize(gobject);
157 }
158
159 static void
160 gmemory_game_allocate(ClutterActor *actor,
161                       const ClutterActorBox *box,
162                       ClutterAllocationFlags flags) {
163         GMemoryGame *game = GMEMORY_GAME(actor);
164         gboolean is_same;
165
166         is_same = clutter_actor_box_equal(box, &game->priv->allocation);
167
168         CLUTTER_ACTOR_CLASS(gmemory_game_parent_class)->allocate(actor, box, flags);
169         game->priv->allocation = *box;
170
171         if(is_same)
172                 return;
173
174         layout_cards(game, FALSE);
175 }
176
177 static void
178 gmemory_game_get_preferred_width(ClutterActor *actor, float for_height,
179                                  float *min_width_p,  float *natural_width_p) {
180         *min_width_p     = GAME_MIN_WIDTH;
181         *natural_width_p = 3 * GAME_MIN_WIDTH;
182 }
183
184 static void
185 gmemory_game_get_preferred_height(ClutterActor *actor, float for_width,
186                                   float *min_height_p, float *natural_height_p) {
187         *min_height_p     = GAME_MIN_HEIGHT;
188         *natural_height_p = 3 * GAME_MIN_HEIGHT;
189 }
190
191 /* Inits. */
192 static void
193 gmemory_game_class_init(GMemoryGameClass *klass) {
194         GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
195         ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS(klass);
196         GParamSpec *pspec;
197
198         gobject_class->dispose      = gmemory_game_dispose;
199         gobject_class->finalize     = gmemory_game_finalize;
200
201         gobject_class->set_property = gmemory_game_set_property;
202         gobject_class->get_property = gmemory_game_get_property;
203
204         actor_class->allocate             = gmemory_game_allocate;
205         actor_class->get_preferred_width  = gmemory_game_get_preferred_width;
206         actor_class->get_preferred_height = gmemory_game_get_preferred_height;
207
208         /* Signals */
209         signals[START_GAME] =
210             g_signal_new("start-game",
211                          G_TYPE_FROM_CLASS (gobject_class),
212                          G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
213                          G_STRUCT_OFFSET(GMemoryGameClass, start_game),
214                          NULL, NULL,
215                          g_cclosure_marshal_VOID__VOID,
216                          G_TYPE_NONE,
217                          0,
218                          NULL);
219
220         signals[END_GAME] =
221             g_signal_new("end-game",
222                          G_TYPE_FROM_CLASS (gobject_class),
223                          G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
224                          G_STRUCT_OFFSET(GMemoryGameClass, end_game),
225                          NULL, NULL,
226                          g_cclosure_marshal_VOID__BOOLEAN,
227                          G_TYPE_NONE,
228                          1,
229                          G_TYPE_BOOLEAN); /* True if game was completed? */
230
231         signals[ATTEMPT_MADE] =
232             g_signal_new("attempt-made",
233                          G_TYPE_FROM_CLASS (gobject_class),
234                          G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
235                          G_STRUCT_OFFSET(GMemoryGameClass, attempt_made),
236                          NULL, NULL,
237                          g_cclosure_marshal_VOID__BOOLEAN,
238                          G_TYPE_NONE,
239                          1,
240                          G_TYPE_BOOLEAN);
241
242         /* Properties. */
243         pspec = g_param_spec_uint("difficulty", "Size",
244                                   "The difficulty of the game.",
245                                   min_difficulty, max_difficulty, default_difficulty,
246                                   G_PARAM_READABLE | G_PARAM_WRITABLE |
247                                   G_PARAM_STATIC_STRINGS);
248         g_object_class_install_property(gobject_class, PROP_DIFFICULTY, pspec);
249
250         pspec = g_param_spec_ulong("elapsed-time", "Elapsed Time",
251                                    "Elapsed time of current game.",
252                                    0, LONG_MAX, 0,
253                                    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
254         g_object_class_install_property(gobject_class, PROP_ELAPSED_TIME, pspec);
255
256         pspec = g_param_spec_uint("attempts", "Attempts", "Number of attempts.",
257                                   0, 1000, 0,
258                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
259         g_object_class_install_property(gobject_class, PROP_ATTEMPTS, pspec);
260
261         pspec = g_param_spec_uint("matches", "Matches", "Number of matches made.",
262                                   0, MAX_MATCHES, 0,
263                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
264         g_object_class_install_property(gobject_class, PROP_MATCHES, pspec);
265
266         pspec = g_param_spec_uint("state", "State", "The games state.",
267                                   GAME_STATE_IDLE, GAME_STATE_FINISHED, GAME_STATE_IDLE,
268                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
269         g_object_class_install_property(gobject_class, PROP_MATCHES, pspec);
270
271         g_type_class_add_private(klass, sizeof(GMemoryGamePrivate));
272 }
273
274 static void
275 gmemory_game_init(GMemoryGame *self) {
276         self->priv = GMEMORY_GAME_GET_PRIVATE(self);
277
278         memset(&self->priv->allocation, 0, sizeof (ClutterActorBox));
279
280         self->priv->difficulty      = default_difficulty;
281         self->priv->curr_pos_assign = g_ptr_array_new();
282         self->priv->matched_pairs   = g_ptr_array_new();
283         self->priv->selected_card   = NULL;
284         self->priv->state           = GAME_STATE_IDLE;
285         self->priv->timer           = g_timer_new();
286         self->priv->card_pairs       = NULL;
287 }
288
289 GMemoryGame *
290 gmemory_game_new() {
291         return g_object_new(GMEMORY_TYPE_GAME, NULL);
292 }
293
294 /* Getters. */
295 guint
296 gmemory_game_get_difficulty(GMemoryGame *game) {
297         g_return_val_if_fail(GMEMORY_IS_GAME(game), 0);
298
299         return game->priv->difficulty;
300 }
301
302 gulong
303 gmemory_game_get_ealpsed_time(GMemoryGame *game) {
304         g_return_val_if_fail(GMEMORY_IS_GAME(game), 0);
305
306         gulong elapsed_time;
307         g_timer_elapsed(game->priv->timer, &elapsed_time);
308         return elapsed_time;
309 }
310
311 guint
312 gmemory_game_get_attempts(GMemoryGame *game) {
313         g_return_val_if_fail(GMEMORY_IS_GAME(game), 0);
314
315         return game->priv->attempts;
316 }
317
318 guint
319 gmemory_game_get_matches(GMemoryGame *game) {
320         g_return_val_if_fail(GMEMORY_IS_GAME(game), 0);
321
322         return game->priv->matches;
323 }
324
325 /* Setters. */
326 void
327 gmemory_game_set_difficulty(GMemoryGame *game, guint difficulty) {
328         GMemoryGamePrivate *priv;
329
330         g_return_if_fail(GMEMORY_IS_GAME(game));
331
332         if (game->priv->difficulty != difficulty) {
333                 /* Make sure we hold a reference throughout. */
334                 g_object_ref(game);
335
336                 game->priv->difficulty = difficulty;
337
338                 /* Reinitialize Game, fire signals to indicate such. */
339                 /* Change card layout here... */
340
341                 clutter_actor_queue_redraw(CLUTTER_ACTOR(game));
342
343                 g_object_notify(G_OBJECT(game), "difficulty");
344                 g_object_unref(game);
345         }
346 }
347
348 void
349 gmemory_game_start(GMemoryGame *game) {
350         /* Start new game. */
351         initialize_new_game(game);
352 }
353
354 /* Start "Private" helper functions. */
355
356 static void
357 set_game_state(GMemoryGame *game, GMemoryGameState state) {
358         GMemoryGamePrivate *priv = GMEMORY_GAME_GET_PRIVATE(game);
359         if (priv->state != state) {
360                 priv->state = state;
361
362                 if (state == GAME_STATE_START) {
363                         g_timer_start(priv->timer);
364                         g_object_notify(G_OBJECT(game), "state");
365
366                         /* Now that the game is started, await the next move. */
367                         priv->state = GAME_STATE_AWAITING_NEXT_MOVE;
368                 } else if (state >= GAME_STATE_FINISHED) {
369                         g_timer_stop(priv->timer);
370
371                         guint pair_count   = g_list_length(priv->card_pairs);
372                         gboolean completed = (priv->matches == pair_count);
373                         g_signal_emit(game, signals[END_GAME], 0, completed);
374                 }
375                 g_object_notify(G_OBJECT(game), "state");
376         }
377 }
378
379 static void
380 hide_unmatched_card_pairs(GMemoryCardPair * card_pair, GMemoryGame * game) {
381         gboolean is_matched = FALSE;
382
383         guint ptr_array_len = game->priv->matched_pairs->len;
384         guint i;
385         for(i = 0; i < ptr_array_len; i++) {
386                 if(card_pair == g_ptr_array_index(game->priv->matched_pairs, i)) {
387                         is_matched = TRUE;
388                         break;
389                 }
390         }
391
392         if(!is_matched) {
393                 gmemory_card_turn(card_pair->card1, FALSE);
394                 gmemory_card_turn(card_pair->card2, FALSE);
395         }
396 }
397
398
399 static void
400 hide_unmatched_cards(GMemoryGame *game) {
401         g_list_foreach(game->priv->card_pairs,
402                        (GFunc)(hide_unmatched_card_pairs),
403                        game);
404 }
405
406 static gboolean
407 check_for_match(GMemoryGame *game, GMemoryCard *card1, GMemoryCard *card2) {
408         gboolean is_match = FALSE;
409
410         guint card1_id = gmemory_card_get_id(card1);
411         guint card2_id = gmemory_card_get_id(card2);
412         if(card1_id == card2_id) {
413                 GList *card_pair_node;
414                 for(card_pair_node = game->priv->card_pairs; card_pair_node;
415                                         card_pair_node = card_pair_node->next) {
416                         GMemoryCardPair *card_pair = (GMemoryCardPair *)(card_pair_node->data);
417                         if(gmemory_card_get_id(card_pair->card1) == card1_id) {
418                                 g_ptr_array_add(game->priv->matched_pairs, card_pair);
419                                 is_match = TRUE;
420                                 game->priv->matches++;
421                                 break;
422                         }
423                 }
424         }
425
426         game->priv->attempts++;
427         g_print("Matches / Attempts = %d/%d (%f\%)\n",
428                 game->priv->matches,
429                 game->priv->attempts,
430                 (game->priv->matches * 100.0) /game->priv->attempts);
431         g_signal_emit(game, signals[ATTEMPT_MADE], 0, is_match);
432
433         return is_match;
434 }
435
436 static GList *
437 get_randomized_positions(GMemoryGame *game) {
438         GList *positions_sorted = NULL;
439         GList *positions_rand = NULL;
440
441         guint difficulty = game->priv->difficulty;
442         guint slots = difficulty*difficulty;
443         if (slots % 2) /* Adjust when slots are odd. */
444                 slots--;
445
446         /* Fill list with numbers. */
447         guint slot;
448         for(slot = 0; slot < slots; slot++)
449                 positions_sorted = g_list_prepend(positions_sorted, GINT_TO_POINTER(slot));
450
451         /* Randomize the list. */
452         GRand *rand_gen = g_rand_new();
453         guint  rand_pos;
454         GList *rand_list_element;
455         guint  shuffles_left = slots;
456         do {
457                 rand_pos = (guint) g_rand_int_range(rand_gen, 0, shuffles_left);
458
459                 rand_list_element = g_list_nth(positions_sorted, rand_pos);
460                 positions_sorted = g_list_remove_link(positions_sorted,
461                                                       rand_list_element);
462                 /* Note: Insert shortest list first. */
463                 positions_rand = g_list_concat(rand_list_element, positions_rand);
464         } while (--shuffles_left > 0);
465
466         return positions_rand;
467 }
468
469 static gboolean
470 on_card_clicked(GMemoryCard *card, ClutterEvent *event, gpointer data)
471 {
472         /* Known bug in clutter_gtk causes double the events to be generated.
473          * Work around this by only accepting every other click, which should be
474          * valid when getting "button-release-event"s.
475          */
476         static gboolean is_first_event = TRUE;
477
478         if(is_first_event) {
479                 GMemoryGame *game = GMEMORY_GAME(data);
480                 GMemoryGamePrivate *priv = GMEMORY_GAME_GET_PRIVATE(game);
481
482                 switch(priv->state)
483                 {
484                 case GAME_STATE_AWAITING_NEXT_MOVE:
485                         set_game_state(game, GAME_STATE_ONE_CARD_CHOSEN);
486                         gmemory_card_turn(card, TRUE);
487                         priv->selected_card = card;
488                         break;
489                 case GAME_STATE_ONE_CARD_CHOSEN:
490                         gmemory_card_turn(card, TRUE);
491                         if(check_for_match(game, card, priv->selected_card))
492                                 set_game_state(game, GAME_STATE_AWAITING_NEXT_MOVE);
493                                 if(priv->matches == g_list_length(priv->card_pairs))
494                                         set_game_state(game, GAME_STATE_FINISHED);
495                         else
496                                 /* This state is just here till animation works. */
497                                 set_game_state(game, GAME_STATE_CHECKING_FOR_MATCH);
498                         break;
499                 case GAME_STATE_CHECKING_FOR_MATCH:
500                         hide_unmatched_cards(game);
501                         set_game_state(game, GAME_STATE_AWAITING_NEXT_MOVE);
502                 default:
503                         set_game_state(game, GAME_STATE_AWAITING_NEXT_MOVE);
504                 }
505         }
506
507         /* FIXME: Comment this out if you are not experiencing the above bug. */
508         is_first_event = !is_first_event;
509
510         return TRUE; /* Stop further handling of this event. */
511 }
512
513 /* FIXME: This function is grossly big. Need to break it up. */
514 static void
515 layout_cards(GMemoryGame *game, gboolean initial_layout) {
516         GMemoryGamePrivate *priv = game->priv;
517
518         guint difficulty = priv->difficulty;
519         GList *card_pairs = g_list_first(priv->card_pairs);
520
521         /* Calculate the width and height of the game. */
522         gfloat alloc_w = (priv->allocation.x2 - priv->allocation.x1) - 2*CARD_SPACING;
523         gfloat alloc_h = (priv->allocation.y2 - priv->allocation.y1) - 2*CARD_SPACING;
524         gfloat game_size = alloc_w < alloc_h ? alloc_w : alloc_h;
525
526         GList *positions = NULL;
527         guint slots = difficulty*difficulty;
528         guint empty_center = 0;
529
530         /* If there is an odd number of slots we leave a blank space (or logo)
531          * in the center.
532          */
533         if(slots % 2)
534                 empty_center = slots/2;
535
536         positions = get_randomized_positions(game);
537
538         /* Calculate card size. */
539         gfloat card_size = (game_size - (difficulty - 1)*CARD_SPACING) / (difficulty);
540
541         /* Calculate game position. */
542         gfloat game_x = (alloc_w < alloc_h) ? 0.0 : (alloc_w - game_size) / 2;
543         gfloat game_y = (alloc_w < alloc_h) ? (alloc_h - game_size) / 2 : 0.0;
544
545         guint assign_len = priv->curr_pos_assign->len;
546         if(assign_len > 0)
547                 g_ptr_array_remove_range(priv->curr_pos_assign, 0, assign_len);
548
549         guint slot;
550         GList *position_node;
551         guint position;
552         GList *card_pair_node;
553         GMemoryCardPair *card_pair;
554         GMemoryCard *card;
555         gfloat pos_x, pos_y;
556         guint row, col;
557         for(row = 0; row < difficulty; row++) {
558                 for(col = 0; col < difficulty; col++) {
559                         /* Get the position from the randomized position list. */
560                         slot = row * difficulty + col;
561
562                         /* If empty_center has been set then skip when there. */
563                         if(empty_center > 0) {
564                                 if(slot == empty_center)
565                                         continue;
566                                 else if(slot > empty_center)
567                                         slot--;
568                         }
569
570                         /* This should only be done once per game. */
571                         if(initial_layout) {
572                                 position_node = g_list_nth(positions, slot);
573                                 position = GPOINTER_TO_INT(position_node->data);
574
575                                 /* Get the card at this position. We rely on
576                                  * truncation when dividing an odd number. */
577                                 card_pair_node = g_list_nth(card_pairs, position / 2);
578                                 card_pair = (GMemoryCardPair *)card_pair_node->data;
579                                 card = (position % 2) ? card_pair->card1 :
580                                                         card_pair->card2;
581                                 g_ptr_array_add(priv->curr_pos_assign, card);
582
583                                 clutter_container_add_actor(CLUTTER_CONTAINER(game),
584                                                             CLUTTER_ACTOR(card));
585                                 /* Set actor to emit events. */
586                                 clutter_actor_set_reactive(CLUTTER_ACTOR(card),
587                                                            TRUE);
588
589                                 g_signal_connect(card, "button-release-event",
590                                                  G_CALLBACK(on_card_clicked), game);
591                         } else {
592                                 card = g_ptr_array_index(priv->curr_pos_assign,
593                                                          slot);
594                         }
595
596                         /* Calculate position and place onto playing area. */
597                         pos_x = col * (card_size + CARD_SPACING) + game_x;
598                         pos_y = row * (card_size + CARD_SPACING) + game_y;
599                         clutter_actor_set_position(CLUTTER_ACTOR(card), pos_x,
600                                                                         pos_y);
601                         clutter_actor_set_size(CLUTTER_ACTOR(card), card_size,
602                                                                     card_size);
603                 }
604         }
605 }
606
607 static void
608 clear_card_pairs(GMemoryGame *game) {
609         if (game->priv->card_pairs) {
610                 GList *card;
611                 GMemoryCardPair *card_pair;
612                 for (card = game->priv->card_pairs; card; card = card->next) {
613                         card_pair = card->data;
614                         clutter_actor_destroy(CLUTTER_ACTOR(card_pair->card1));
615                         clutter_actor_destroy(CLUTTER_ACTOR(card_pair->card2));
616                         g_free(card_pair);
617                 }
618                 g_list_free(game->priv->card_pairs);
619                 game->priv->card_pairs = NULL;
620         }
621 }
622
623 static void
624 initialize_cardpairs(GMemoryGame *game) {
625         if (game->priv->card_pairs)
626                 clear_card_pairs(game);
627
628         guint difficulty = game->priv->difficulty;
629         guint pair_cnt = (difficulty*difficulty)/2;
630
631         guint id;
632         for (id = 1; id <= pair_cnt; id++) {
633                 GMemoryCardPair *card_pair =
634                          (GMemoryCardPair *) g_malloc(sizeof(GMemoryCardPair));
635                 card_pair->card1 = GMEMORY_CARD(gmemory_card_new(id));
636                 card_pair->card2 = GMEMORY_CARD(gmemory_card_new(id));
637                 game->priv->card_pairs = g_list_prepend(game->priv->card_pairs,
638                                                        card_pair);
639         }
640 }
641
642 static void
643 initialize_new_game(GMemoryGame *game) {
644         initialize_cardpairs(game);
645
646         guint match_count = game->priv->matched_pairs->len;
647         if(match_count > 0)
648                 g_ptr_array_remove_range(game->priv->matched_pairs, 0, match_count);
649
650         game->priv->attempts = 0;
651         game->priv->matches  = 0;
652         game->priv->selected_card = NULL;
653         layout_cards(game, TRUE);
654
655         /* Should actually be fired on first card selection. */
656         g_signal_emit(game, signals[START_GAME], 0);
657         set_game_state(game, GAME_STATE_START);
658 }