1 /*
2  * Copyright © 1998, 2003 Jonathan Blandford <jrb@alum.mit.edu>
3  * Copyright © 2007, 2011 Christian Persch
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 
21 #include <string.h>
22 #include <unistd.h>
23 #include <time.h>
24 
25 #include <libguile.h>
26 
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 
30 #include "ar-debug.h"
31 #include "ar-runtime.h"
32 #include "ar-string-utils.h"
33 
34 #include "conf.h"
35 #include "util.h"
36 
37 #include "game.h"
38 
39 #define DELAYED_CALLBACK_DELAY (50)
40 
41 #define I_(string) g_intern_static_string (string)
42 
43 struct _AisleriotGameClass
44 {
45   GObjectClass parent_class;
46 };
47 
48 enum {
49   NEW_GAME_LAMBDA,
50   BUTTON_PRESSED_LAMBDA,
51   BUTTON_RELEASED_LAMBDA,
52   BUTTON_CLICKED_LAMBDA,
53   BUTTON_DOUBLE_CLICKED_LAMBDA,
54   GAME_OVER_LAMBDA,
55   WINNING_GAME_LAMBDA,
56   HINT_LAMBDA,
57   GET_OPTIONS_LAMBDA,
58   APPLY_OPTIONS_LAMBDA,
59   TIMEOUT_LAMBDA,
60   DROPPABLE_LAMBDA,
61   DEALABLE_LAMBDA,
62   N_LAMBDAS,
63   LAST_MANDATORY_LAMBDA = TIMEOUT_LAMBDA
64 };
65 
66 static const char lambda_names[] = {
67   "new-game\0"
68   "button-pressed\0"
69   "button-released\0"
70   "button-clicked\0"
71   "button-double-clicked\0"
72   "game-over\0"
73   "winning-game\0"
74   "hint\0"
75   "get-options\0"
76   "apply-options\0"
77   "timeout\0"
78   "droppable\0"
79   "dealable\0"
80 };
81 
82 struct _AisleriotGame
83 {
84   GObject parent_instance;
85 
86   GPtrArray *slots;
87 
88   char *game_module;
89 
90   GRand *rand;
91   GRand *saved_rand;
92 
93   guint delayed_call_timeout_id;
94 
95   GTimer *timer;
96 
97   int timeout;
98   char *score;
99 
100   double width;
101   double height;
102 
103   /* Game callbacks */
104   SCM lambdas[N_LAMBDAS];
105 
106   guint click_to_move : 1;
107   guint can_undo : 1;
108   guint can_redo : 1;
109   guint can_deal : 1;
110   guint show_score : 1;
111   guint features : 3; /* enough bits for ALL_FEATURES */
112   guint state : 3; /* enough bits for LAST_GAME_STATE */
113   guint had_exception : 1;
114   guint paused : 1;
115 };
116 
117 /* The one and only game */
118 AisleriotGame *app_game;
119 
120 enum
121 {
122   PROP_0,
123   PROP_CAN_UNDO,
124   PROP_CAN_REDO,
125   PROP_CAN_DEAL,
126   PROP_GAME_FILE,
127   PROP_SCORE,
128   PROP_STATE,
129 };
130 
131 enum
132 {
133   GAME_TYPE,
134   GAME_CLEARED,
135   GAME_NEW,
136   GAME_MESSAGE,
137   SLOT_CHANGED,
138   EXCEPTION,
139   LAST_SIGNAL
140 };
141 
142 static guint signals[LAST_SIGNAL];
143 
144 G_DEFINE_TYPE (AisleriotGame, aisleriot_game, G_TYPE_OBJECT);
145 
146 /* helper functions */
147 
148 static void
clear_delayed_call(AisleriotGame * game)149 clear_delayed_call (AisleriotGame *game)
150 {
151   if (game->delayed_call_timeout_id != 0) {
152     g_source_remove (game->delayed_call_timeout_id);
153     game->delayed_call_timeout_id = 0;
154   }
155 }
156 
157 static void
set_game_state(AisleriotGame * game,guint state)158 set_game_state (AisleriotGame *game,
159                 guint state)
160 {
161   if (game->state != state) {
162     game->state = state;
163 
164     if (state == GAME_RUNNING) {
165       /* Reset the timer */
166       g_timer_start (game->timer);
167     } else if (state >= GAME_OVER) {
168       /* Stop the timer now so we will record the right time. See bug #514239. */
169       g_timer_stop (game->timer);
170     }
171 
172     g_object_notify (G_OBJECT (game), "state");
173   }
174 }
175 
176 static void
set_game_undoable(AisleriotGame * game,gboolean enabled)177 set_game_undoable (AisleriotGame *game,
178                    gboolean enabled)
179 {
180   if (enabled != game->can_undo) {
181     game->can_undo = enabled;
182 
183     g_object_notify (G_OBJECT (game), "can-undo");
184   }
185 }
186 
187 static void
set_game_redoable(AisleriotGame * game,gboolean enabled)188 set_game_redoable (AisleriotGame *game,
189                    gboolean enabled)
190 {
191   if (enabled != game->can_redo) {
192     game->can_redo = enabled;
193 
194     g_object_notify (G_OBJECT (game), "can-redo");
195   }
196 }
197 
198 static void
set_game_dealable(AisleriotGame * game,gboolean enabled)199 set_game_dealable (AisleriotGame *game,
200                    gboolean enabled)
201 {
202   if (enabled != game->can_deal) {
203     game->can_deal = enabled != FALSE;
204 
205     g_object_notify (G_OBJECT (game), "can-deal");
206   }
207 }
208 
209 /* Yes, there is a race condition here, but since this is all
210    per user they're going to have to be either playing impossibly fast
211    or using sharing an account. In the later case they won't be caring
212    about the statistics. */
213 static void
update_statistics(AisleriotGame * game)214 update_statistics (AisleriotGame *game)
215 {
216   AisleriotStatistic current_stats;
217   time_t t;
218 
219   aisleriot_conf_get_statistic (game->game_module, &current_stats);
220 
221   current_stats.total++;
222 
223   if (game->state == GAME_WON) {
224     current_stats.wins++;
225 
226     t = (time_t) (g_timer_elapsed (game->timer, NULL) + 0.5);
227     if (t > 0) {
228       if ((current_stats.best == 0) || (t < current_stats.best)) {
229 	current_stats.best = t;
230       }
231       if (t > current_stats.worst) {
232 	current_stats.worst = t;
233       }
234     }
235   }
236 
237   aisleriot_conf_set_statistic (game->game_module, &current_stats);
238 }
239 
240 static void
clear_slots(AisleriotGame * game,gboolean notify)241 clear_slots (AisleriotGame *game,
242              gboolean notify)
243 {
244   guint i, n_slots;
245 
246   n_slots = game->slots->len;
247   for (i = 0; i < n_slots; ++i) {
248     ArSlot *slot = game->slots->pdata[i];
249 
250     g_ptr_array_free (slot->card_images, TRUE);
251     g_byte_array_free (slot->cards, TRUE);
252 
253     g_slice_free (ArSlot, slot);
254   }
255 
256   g_ptr_array_set_size (game->slots, 0);
257 
258   if (notify) {
259     g_signal_emit (game, signals[GAME_CLEARED], 0);
260   }
261 }
262 
263 static ArSlot *
get_slot(AisleriotGame * game,gint slotid)264 get_slot (AisleriotGame *game,
265           gint slotid)
266 {
267   guint i, n_slots;
268 
269   n_slots = game->slots->len;
270   for (i = 0; i < n_slots; ++i) {
271     ArSlot *hslot = game->slots->pdata[i];
272 
273     if (hslot->id == slotid)
274       return hslot;
275   }
276 
277   return NULL;
278 }
279 
280 
281 /* Scheme helpers */
282 
283 typedef struct {
284   SCM lambda;
285   SCM *args;
286   gsize n_args;
287 } CallData;
288 
289 static char *
cscmi_exception_get_backtrace(SCM tag,SCM throw_args)290 cscmi_exception_get_backtrace (SCM tag, SCM throw_args)
291 {
292   AisleriotGame *game = app_game;
293   SCM port;
294   SCM stack;
295   GPtrArray *slots;
296   guint i, n_slots;
297   GString *message;
298   char *string;
299 
300   scm_dynwind_begin (0);
301 
302   message = g_string_sized_new (1024);
303 
304   g_string_append_printf (message, "Variation: %s\n", aisleriot_game_get_game_module (game));
305 #if 0
306   g_string_append_printf (message, "Seed: %u\n", game->seed);
307 #endif
308 
309   g_string_append (message, "Scheme error:\n\t");
310 
311   port = scm_open_output_string ();
312   scm_display (throw_args, port);
313   string = scm_to_utf8_string (scm_get_output_string (port));
314   scm_dynwind_free (string);
315   scm_close_output_port (port);
316   g_string_append (message, string);
317 
318   port = scm_open_output_string ();
319   g_string_append (message, "\nScheme tag:\n\t");
320   scm_display (tag, port);
321   string = scm_to_utf8_string (scm_get_output_string (port));
322   scm_dynwind_free (string);
323   scm_close_output_port (port);
324   g_string_append (message, string);
325 
326   g_string_append (message, "\n\nBacktrace:\n");
327   stack = scm_make_stack (SCM_BOOL_T, SCM_EOL);
328   if (!scm_is_false (stack)) {
329     port = scm_open_output_string ();
330     scm_display_backtrace (stack, port, SCM_UNDEFINED, SCM_UNDEFINED);
331     string = scm_to_utf8_string (scm_get_output_string (port));
332     scm_dynwind_free (string);
333     scm_close_output_port (port);
334     g_string_append (message, string);
335   } else {
336     g_string_append (message, "\tNo backtrace available.\n");
337   }
338 
339   g_string_append (message, "\n\nDeck State:\n");
340 
341   slots = aisleriot_game_get_slots (game);
342 
343   n_slots = slots->len;
344   if (n_slots > 0) {
345     for (i = 0; i < n_slots; ++i) {
346       ArSlot *slot = slots->pdata[i];
347       GByteArray *cards = slot->cards;
348       guint n_cards;
349 
350       g_string_append_printf (message, "\tSlot %d\n", slot->id);
351 
352       n_cards = cards->len;
353       if (n_cards > 0) {
354         guint8 *data = cards->data;
355         guint count;
356 
357         for (count = 0; count < n_cards; ++count) {
358           Card card = CARD (data[count]);
359 
360           if (count == 0) {
361             g_string_append_c (message, '\t');
362             g_string_append_c (message, '\t');
363           } else {
364             g_string_append_c (message, ' ');
365             g_string_append_c (message, ',');
366           }
367 
368           g_string_append_printf (message,
369                                   "(%d %d %s)",
370                                   CARD_GET_SUIT (card),
371                                   CARD_GET_RANK (card),
372                                   /* See c2scm_card below */
373                                   CARD_GET_FACE_DOWN (card) ? "#f" : "#t");
374 
375           count++;
376           if (count == 5) {
377             g_string_append_c (message, '\n');
378             count = 0;
379           }
380         }
381         if (count != 0) {
382           g_string_append_c (message, '\n');
383         }
384       } else {
385         g_string_append (message,"\t\t(Empty)\n");
386       }
387     }
388   } else {
389     g_string_append (message, "\tNo cards in deck\n");
390   }
391 
392   scm_dynwind_end ();
393 
394   return g_string_free (message, FALSE);
395 }
396 
397 /* Called when we get an exception from guile.  We launch bug-buddy with the
398  * exception information:
399  */
400 static SCM
game_scm_pre_unwind_handler(void * user_data,SCM tag,SCM throw_args)401 game_scm_pre_unwind_handler (void *user_data,
402                              SCM tag,
403                              SCM throw_args)
404 {
405   GError **error = user_data;
406   char *message;
407 
408   /* Not interested in errors, or already had an exception */
409   if (error == NULL || *error != NULL)
410     return SCM_UNDEFINED;
411 
412   message = cscmi_exception_get_backtrace (tag, throw_args);
413   g_set_error_literal (error,
414                        AISLERIOT_GAME_ERROR,
415                        GAME_ERROR_EXCEPTION,
416                        message ? message
417                                : "A scheme exception occurred, but there was no exception info");
418   return SCM_UNDEFINED;
419 }
420 
421 static SCM
game_scm_catch_handler(void * user_data,SCM tag,SCM throw_args)422 game_scm_catch_handler (void *user_data,
423                         SCM tag,
424                         SCM throw_args)
425 {
426   return SCM_UNDEFINED;
427 }
428 
429 static SCM
game_scm_call_lambda(void * user_data)430 game_scm_call_lambda (void *user_data)
431 {
432   CallData *data = (CallData *) user_data;
433 
434 #if SCM_MAJOR_VERSION >= 2
435   return scm_call_n (data->lambda, data->args, data->n_args);
436 #else
437   /* Guile 1.8 lacks the scm_call_n function */
438   switch (data->n_args) {
439     case 0:
440       return scm_call_0 (data->lambda);
441     case 1:
442       return scm_call_1 (data->lambda, data->args[0]);
443     case 2:
444       return scm_call_2 (data->lambda, data->args[0], data->args[1]);
445     case 3:
446       return scm_call_3 (data->lambda, data->args[0], data->args[1], data->args[2]);
447     default:
448       g_assert_not_reached ();
449   }
450 #endif
451 }
452 
453 static gboolean
game_scm_call(SCM lambda,SCM * args,gsize n_args,SCM * retval)454 game_scm_call (SCM lambda,
455                SCM *args,
456                gsize n_args,
457                SCM *retval)
458 {
459   CallData data = { lambda, args, n_args };
460   GError *error = NULL;
461   SCM rv;
462 
463   rv = scm_c_catch (SCM_BOOL_T,
464                     game_scm_call_lambda, &data,
465                     game_scm_catch_handler, NULL,
466                     game_scm_pre_unwind_handler, &error);
467   if (error) {
468     app_game->had_exception = TRUE;
469 
470     g_signal_emit (app_game, signals[EXCEPTION], 0, error);
471     g_error_free (error);
472 
473     /* This game is over, but don't count it in the statistics */
474     set_game_state (app_game, GAME_LOADED);
475 
476     return FALSE;
477   }
478 
479   if (retval)
480     *retval = rv;
481 
482   return TRUE;
483 }
484 
485 static gboolean
game_scm_call_by_name(const char * name,SCM * args,gsize n_args,SCM * retval)486 game_scm_call_by_name (const char *name,
487                        SCM *args,
488                        gsize n_args,
489                        SCM *retval)
490 {
491   SCM lambda;
492 
493   lambda = scm_c_eval_string (name);
494 
495   if (!game_scm_call (lambda, args, n_args, retval))
496     return FALSE;
497 
498   scm_remember_upto_here_1 (lambda);
499   return TRUE;
500 }
501 
502 static SCM
c2scm_card(Card card)503 c2scm_card (Card card)
504 {
505   return scm_cons (scm_from_uint (CARD_GET_RANK (card)),
506                    scm_cons (scm_from_uint (CARD_GET_SUIT (card)),
507                              scm_cons (SCM_BOOL (!CARD_GET_FACE_DOWN (card)),
508                                        SCM_EOL)));
509 }
510 
511 static void
scm2c_card(SCM card_data,Card * card)512 scm2c_card (SCM card_data,
513             Card *card)
514 {
515   guint rank, suit, face_down;
516 
517   card->value = 0;
518 
519   rank = scm_to_int (SCM_CAR (card_data));
520   suit = scm_to_int (SCM_CADR (card_data));
521   face_down = !(scm_is_true (SCM_CADDR (card_data)));
522 
523   card->attr.rank = rank;
524   card->attr.suit = suit;
525   card->attr.face_down = face_down;
526 }
527 
528 static SCM
c2scm_deck(guint8 * cards,guint n_cards)529 c2scm_deck (guint8 *cards,
530             guint n_cards)
531 {
532   SCM scm_cards;
533   guint i;
534 
535   scm_cards = SCM_EOL;
536   for (i = 0; i < n_cards; ++i) {
537     scm_cards = scm_cons (c2scm_card (CARD (cards[i])), scm_cards);
538   }
539 
540   return scm_cards;
541 }
542 
543 static void
cscmi_slot_set_cards(ArSlot * slot,SCM cards)544 cscmi_slot_set_cards (ArSlot *slot,
545                       SCM cards)
546 {
547   AisleriotGame *game = app_game;
548   SCM list_el;
549   guint8 *data = NULL;
550   guint i, n_cards = 0;
551 
552   if (scm_is_true (scm_list_p (cards))) {
553     for (list_el = cards; list_el != SCM_EOL; list_el = SCM_CDR (list_el)) {
554       ++n_cards;
555     }
556 
557     data = g_alloca (n_cards);
558     i = n_cards;
559 
560     for (list_el = cards; list_el != SCM_EOL; list_el = SCM_CDR (list_el)) {
561       Card card;
562 
563       scm2c_card (SCM_CAR (list_el), &card);
564       data[--i] = CARD_UINT (card);
565     }
566   }
567 
568   /* Don't set the new cards if the same cards are already there.
569    * This saves us lots of updates on undo/redo.
570    */
571   if (slot->cards->len == n_cards &&
572       memcmp (slot->cards->data, data, n_cards) == 0)
573     return;
574 
575   g_byte_array_set_size (slot->cards, 0);
576 
577   aisleriot_game_slot_add_cards (game, slot, data, n_cards);
578 }
579 
580 static SCM
cscmi_add_slot(SCM slot_data)581 cscmi_add_slot (SCM slot_data)
582 {
583   AisleriotGame *game = app_game;
584   ArSlot *slot;
585   gboolean expanded_down = FALSE;
586   gboolean expanded_right = FALSE;
587   int expansion_depth = 0;
588   ArSlotType type = AR_SLOT_UNKNOWN;
589   SCM slot_placement, slot_type;
590 
591   if (game->state > GAME_BEGIN) {
592     return scm_throw (scm_from_locale_symbol ("aisleriot-invalid-call"),
593                       scm_list_1 (scm_from_utf8_string ("Cannot add a new slot after the game has started.")));
594   }
595 
596 #define EQUALS_SYMBOL(string,object) (scm_is_true (scm_equal_p (scm_from_locale_symbol (string), object)))
597 
598   slot_placement = SCM_CADDR (slot_data);
599   if (EQUALS_SYMBOL ("expanded", SCM_CAR (slot_placement))) {
600     expanded_down = TRUE;
601   } else if (EQUALS_SYMBOL ("expanded-right", SCM_CAR (slot_placement))) {
602     expanded_right = TRUE;
603   } else if (EQUALS_SYMBOL ("partially-expanded", SCM_CAR (slot_placement))) {
604     expanded_down = TRUE;
605     expansion_depth = scm_to_int (SCM_CADDR (slot_placement));
606   } else if (EQUALS_SYMBOL ("partially-expanded-right", SCM_CAR (slot_placement))) {
607     expanded_right = TRUE;
608     expansion_depth = scm_to_int (SCM_CADDR (slot_placement));
609   }
610 
611 #undef CHECK_EXPANSION
612 
613   /* 3rd argument is the slot type (optionally) */
614   slot_type = SCM_CDDDR (slot_data);
615   if (slot_type != SCM_EOL) {
616     if (EQUALS_SYMBOL ("chooser", SCM_CAR (slot_type))) {
617       type = AR_SLOT_CHOOSER;
618     } else if (EQUALS_SYMBOL ("foundation", SCM_CAR (slot_type))) {
619       type = AR_SLOT_FOUNDATION;
620     } else if (EQUALS_SYMBOL ("reserve", SCM_CAR (slot_type))) {
621       type = AR_SLOT_RESERVE;
622     } else if (EQUALS_SYMBOL ("stock", SCM_CAR (slot_type))) {
623       type = AR_SLOT_STOCK;
624     } else if (EQUALS_SYMBOL ("tableau", SCM_CAR (slot_type))) {
625       type = AR_SLOT_TABLEAU;
626     } else if (EQUALS_SYMBOL ("waste", SCM_CAR (slot_type))) {
627       type = AR_SLOT_WASTE;
628     }
629   }
630 
631 #undef EQUALS_SYMBOL
632 
633 #ifdef GNOME_ENABLE_DEBUG
634   _AR_DEBUG_IF (AR_DEBUG_SCHEME) {
635     static const char *types[] = { "unknown", "chooser", "foundation", "reserve", "stock", "tableau", "waste" };
636 
637     ar_debug_print (AR_DEBUG_SCHEME,
638                         "Adding new slot %d type %s\n",
639                         scm_to_int (SCM_CAR (slot_data)), types[type]);
640   }
641 #endif /* GNOME_ENABLE_DEBUG */
642 
643   /* create and initialize slot */
644   slot = g_slice_new0 (ArSlot);
645 
646   g_ptr_array_add (game->slots, slot);
647 
648   slot->id = scm_to_int (SCM_CAR (slot_data));
649   slot->type = type;
650 
651   slot->cards = g_byte_array_sized_new (SLOT_CARDS_N_PREALLOC);
652   slot->exposed = 0;
653   slot->x = scm_to_double (SCM_CAR (SCM_CADR (SCM_CADDR (slot_data))));
654   slot->y = scm_to_double (SCM_CADR (SCM_CADR (SCM_CADDR (slot_data))));
655 
656   slot->expansion_depth = expansion_depth;
657 
658   slot->expansion.dx = 0.0;
659   slot->expanded_down = expanded_down != FALSE;
660   slot->expanded_right = expanded_right != FALSE;
661 
662   slot->card_images = g_ptr_array_sized_new (SLOT_CARDS_N_PREALLOC);
663 
664   slot->needs_update = TRUE;
665 
666   /* this will update the slot length too */
667   cscmi_slot_set_cards (slot, SCM_CADR (slot_data));
668 
669   return SCM_EOL;
670 }
671 
672 /* Scheme functions */
673 
674 static SCM
scm_undo_set_sensitive(SCM in_state)675 scm_undo_set_sensitive (SCM in_state)
676 {
677   AisleriotGame *game = app_game;
678   gboolean state;
679 
680   state = scm_is_true (in_state) ? TRUE : FALSE;
681   set_game_undoable (game, state);
682 
683   return SCM_EOL;
684 }
685 
686 static SCM
scm_redo_set_sensitive(SCM in_state)687 scm_redo_set_sensitive (SCM in_state)
688 {
689   AisleriotGame *game = app_game;
690   gboolean state;
691 
692   state = scm_is_true (in_state) ? TRUE : FALSE;
693   set_game_redoable (game, state);
694 
695   return SCM_EOL;
696 }
697 
698 static SCM
scm_dealable_set_sensitive(SCM in_state)699 scm_dealable_set_sensitive (SCM in_state)
700 {
701   AisleriotGame *game = app_game;
702   gboolean state;
703 
704   state = scm_is_true (in_state) ? TRUE : FALSE;
705   set_game_dealable (game, state);
706 
707   return SCM_EOL;
708 }
709 
710 static SCM
scm_get_feature_word(void)711 scm_get_feature_word (void)
712 {
713   AisleriotGame *game = app_game;
714 
715   g_return_val_if_fail (game != NULL, SCM_EOL);
716 
717   return scm_from_uint (game->features);
718 }
719 
720 static SCM
scm_set_feature_word(SCM features)721 scm_set_feature_word (SCM features)
722 {
723   AisleriotGame *game = app_game;
724 
725   g_return_val_if_fail (game != NULL, SCM_EOL);
726 
727   game->features = scm_to_uint (features);
728 
729   return SCM_EOL;
730 }
731 
732 static SCM
scm_set_statusbar_message(SCM message)733 scm_set_statusbar_message (SCM message)
734 {
735   AisleriotGame *game = app_game;
736   char *str;
737 
738   if (!scm_is_string (message))
739     return SCM_EOL;
740 
741   scm_dynwind_begin (0);
742 
743   str = scm_to_utf8_string (message);
744   scm_dynwind_free (str);
745   if (!str)
746     goto out;
747 
748   g_signal_emit (game, signals[GAME_MESSAGE], 0, str);
749 
750 out:
751   scm_dynwind_end ();
752 
753   return SCM_EOL;
754 }
755 
756 static SCM
scm_reset_surface(void)757 scm_reset_surface (void)
758 {
759   AisleriotGame *game = app_game;
760 
761   clear_slots (game, TRUE);
762   return SCM_EOL;
763 }
764 
765 static SCM
scm_set_slot_x_expansion(SCM scm_slot_id,SCM new_exp_val)766 scm_set_slot_x_expansion (SCM scm_slot_id,
767                           SCM new_exp_val)
768 {
769   AisleriotGame *game = app_game;
770   ArSlot *slot;
771 
772   slot = get_slot (game, scm_to_int (scm_slot_id));
773 
774   /* We should only set the x expansion for right-expanded slots! */
775   g_return_val_if_fail (slot->expanded_right, SCM_EOL);
776   /* Cannot set x and y expansion at the same time */
777   g_return_val_if_fail (!slot->dy_set, SCM_EOL);
778 
779   slot->expansion.dx = scm_to_double (new_exp_val);
780   slot->dx_set = TRUE;
781 
782   /* We don't need to emit the slot-changed signal here,
783    * since we should be here only during game initialisation,
784    * which means that there will be a slot-changed later anyway.
785    */
786   return SCM_EOL;
787 }
788 
789 static SCM
scm_set_slot_y_expansion(SCM scm_slot_id,SCM new_exp_val)790 scm_set_slot_y_expansion (SCM scm_slot_id,
791                           SCM new_exp_val)
792 {
793   AisleriotGame *game = app_game;
794   ArSlot *slot;
795 
796   slot = get_slot (game, scm_to_int (scm_slot_id));
797 
798   /* We should only set the y expansion for down-expanded slots! */
799   g_return_val_if_fail (slot->expanded_down, SCM_EOL);
800   /* Cannot set x and y expansion at the same time */
801   g_return_val_if_fail (!slot->dx_set, SCM_EOL);
802 
803   slot->expansion.dy = scm_to_double (new_exp_val);
804   slot->dy_set = TRUE;
805 
806   /* We don't need to emit the slot-changed signal here,
807    * since we should be here only during game initialisation,
808    * which means that there will be a slot-changed later anyway.
809    */
810   return SCM_EOL;
811 }
812 
813 static SCM
scm_get_slot(SCM scm_slot_id)814 scm_get_slot (SCM scm_slot_id)
815 {
816   AisleriotGame *game = app_game;
817   ArSlot *slot;
818 
819   slot = get_slot (game, scm_to_int (scm_slot_id));
820 
821   if (!slot)
822     return SCM_EOL;
823 
824   return scm_cons (scm_slot_id,
825                    scm_cons (c2scm_deck (slot->cards->data, slot->cards->len),
826                              SCM_EOL));
827 }
828 
829 static SCM
scm_set_cards(SCM scm_slot_id,SCM new_cards)830 scm_set_cards (SCM scm_slot_id,
831                SCM new_cards)
832 {
833   AisleriotGame *game = app_game;
834   ArSlot *slot;
835 
836   slot = get_slot (game, scm_to_int (scm_slot_id));
837 
838   cscmi_slot_set_cards (slot, new_cards);
839 
840   return SCM_BOOL_T;
841 }
842 
843 static SCM
scm_set_lambda(SCM start_game_lambda,SCM pressed_lambda,SCM released_lambda,SCM clicked_lambda,SCM dbl_clicked_lambda,SCM game_over_lambda,SCM winning_game_lambda,SCM hint_lambda,SCM rest)844 scm_set_lambda (SCM start_game_lambda,
845                 SCM pressed_lambda,
846                 SCM released_lambda,
847                 SCM clicked_lambda,
848                 SCM dbl_clicked_lambda,
849                 SCM game_over_lambda,
850                 SCM winning_game_lambda,
851                 SCM hint_lambda,
852                 SCM rest)
853 {
854   AisleriotGame *game = app_game;
855 
856   game->lambdas[NEW_GAME_LAMBDA] = start_game_lambda;
857   game->lambdas[BUTTON_PRESSED_LAMBDA] = pressed_lambda;
858   game->lambdas[BUTTON_RELEASED_LAMBDA] = released_lambda;
859   game->lambdas[BUTTON_CLICKED_LAMBDA] = clicked_lambda;
860   game->lambdas[BUTTON_DOUBLE_CLICKED_LAMBDA] = dbl_clicked_lambda;
861   game->lambdas[GAME_OVER_LAMBDA] = game_over_lambda;
862   game->lambdas[WINNING_GAME_LAMBDA] = winning_game_lambda;
863   game->lambdas[HINT_LAMBDA] = hint_lambda;
864 
865   game->lambdas[GET_OPTIONS_LAMBDA] = SCM_CAR (rest);
866   rest = SCM_CDR (rest);
867 
868   game->lambdas[APPLY_OPTIONS_LAMBDA] = SCM_CAR (rest);
869   rest = SCM_CDR (rest);
870 
871   game->lambdas[TIMEOUT_LAMBDA] = SCM_CAR (rest);
872   rest = SCM_CDR (rest);
873 
874   if (game->features & FEATURE_DROPPABLE) {
875     game->lambdas[DROPPABLE_LAMBDA] = SCM_CAR (rest);
876     rest = SCM_CDR (rest);
877   } else {
878     game->lambdas[DROPPABLE_LAMBDA] = SCM_UNDEFINED;
879   }
880 
881   if (game->features & FEATURE_DEALABLE) {
882     game->lambdas[DEALABLE_LAMBDA] = SCM_CAR (rest);
883     rest = SCM_CDR (rest);
884   } else {
885     game->lambdas[DEALABLE_LAMBDA] = SCM_UNDEFINED;
886   }
887 
888   return SCM_EOL;
889 }
890 
891 static SCM
scm_set_lambda_x(SCM symbol,SCM lambda)892 scm_set_lambda_x (SCM symbol,
893                   SCM lambda)
894 {
895   AisleriotGame *game = app_game;
896   const char *lambda_name;
897   int i;
898 
899   lambda_name = lambda_names;
900   for (i = 0; i < N_LAMBDAS; ++i) {
901     if (scm_is_true (scm_equal_p (symbol, scm_from_locale_symbol (lambda_name)))) {
902       game->lambdas[i] = lambda;
903       return SCM_EOL;
904     }
905 
906     lambda_name += strlen (lambda_name) + 1;
907   }
908 
909   return scm_throw (scm_from_locale_symbol ("aisleriot-invalid-call"),
910                     scm_list_1 (scm_from_utf8_string ("Unknown lambda name in set-lambda!")));
911 }
912 
913 static SCM
scm_myrandom(SCM range)914 scm_myrandom (SCM range)
915 {
916   AisleriotGame *game = app_game;
917 
918   return scm_from_uint32 (g_rand_int_range (game->rand, 0, scm_to_int (range)));
919 }
920 
921 static SCM
scm_click_to_move_p(void)922 scm_click_to_move_p (void)
923 {
924   /* This only affects elevator and escalator games. Their code claims
925    * that in click-to-move makes no sense to move the cards away, but that's
926    * bogus. Just always return FALSE here instead of
927    * game->click_to_move ? SCM_BOOL_T : SCM_BOOL_F
928    */
929   return SCM_BOOL_F;
930 }
931 
932 static SCM
scm_update_score(SCM new_score)933 scm_update_score (SCM new_score)
934 {
935   AisleriotGame *game = app_game;
936   char *score;
937 
938   score = scm_to_utf8_string (new_score);
939   if (g_strcmp0 (score, game->score) != 0) {
940     free (game->score);
941     game->score = score;
942 
943     g_object_notify (G_OBJECT (game), "score");
944   } else {
945     free (score);
946   }
947 
948   return new_score;
949 }
950 
951 static SCM
scm_set_timeout(SCM new)952 scm_set_timeout (SCM new)
953 {
954   AisleriotGame *game = app_game;
955 
956   g_warning ("(set-timeout) unimplemented\n");
957 
958   game->timeout = scm_to_int (new);
959 
960   return new;
961 }
962 
963 static SCM
scm_get_timeout(void)964 scm_get_timeout (void)
965 {
966   AisleriotGame *game = app_game;
967 
968   g_warning ("(get-timeout) unimplemented\n");
969 
970   return scm_from_int (game->timeout);
971 }
972 
973 static void
scm_delayed_call_destroy_data(SCM callback)974 scm_delayed_call_destroy_data (SCM callback)
975 {
976   AisleriotGame *game = app_game;
977 
978   scm_gc_unprotect_object (callback);
979 
980   game->delayed_call_timeout_id = 0;
981 }
982 
983 /* @callback is GC protected during this call! */
984 static gboolean
scm_execute_delayed_function(SCM callback)985 scm_execute_delayed_function (SCM callback)
986 {
987   AisleriotGame *game = app_game;
988 
989   /* We set game->delayed_call_timeout_id to 0 _before_ calling |callback|,
990    * since it might install a new delayed call.
991    */
992   game->delayed_call_timeout_id = 0;
993 
994   if (!game_scm_call (callback, NULL, 0, NULL))
995     return FALSE;
996 
997   aisleriot_game_test_end_of_game (game);
998 
999   return FALSE;
1000 }
1001 
1002 static SCM
scm_delayed_call(SCM callback)1003 scm_delayed_call (SCM callback)
1004 {
1005   AisleriotGame *game = app_game;
1006 
1007   /* We can only have one pending delayed call! */
1008   if (game->delayed_call_timeout_id != 0) {
1009     return scm_throw (scm_from_locale_symbol ("aisleriot-invalid-call"),
1010                       scm_list_1 (scm_from_utf8_string ("Already have a delayed callback pending.")));
1011   }
1012 
1013   /* We need to protect the callback data from being GC'd until the
1014    * timeout has run.
1015    */
1016   scm_gc_protect_object (callback);
1017 
1018   g_timeout_add_full (G_PRIORITY_LOW,
1019                       DELAYED_CALLBACK_DELAY,
1020                       (GSourceFunc) scm_execute_delayed_function,
1021                       callback,
1022                       (GDestroyNotify) scm_delayed_call_destroy_data);
1023 
1024   return SCM_BOOL_T;
1025 }
1026 
1027 static void
cscm_init(void * data G_GNUC_UNUSED)1028 cscm_init (void *data G_GNUC_UNUSED)
1029 {
1030   /* Let the scheme side of things know about our C functions. */
1031   scm_c_define_gsubr ("set-feature-word!", 1, 0, 0, scm_set_feature_word);
1032   scm_c_define_gsubr ("get-feature-word", 0, 0, 0, scm_get_feature_word);
1033   scm_c_define_gsubr ("set-statusbar-message-c", 1, 0, 0,
1034                       scm_set_statusbar_message);
1035   scm_c_define_gsubr ("reset-surface", 0, 0, 0, scm_reset_surface);
1036   scm_c_define_gsubr ("add-slot", 1, 0, 0, cscmi_add_slot);
1037   scm_c_define_gsubr ("get-slot", 1, 0, 0, scm_get_slot);
1038   scm_c_define_gsubr ("set-cards-c!", 2, 0, 0, scm_set_cards);
1039   scm_c_define_gsubr ("set-slot-y-expansion!", 2, 0, 0,
1040                       scm_set_slot_y_expansion);
1041   scm_c_define_gsubr ("set-slot-x-expansion!", 2, 0, 0,
1042                       scm_set_slot_x_expansion);
1043   scm_c_define_gsubr ("set-lambda", 8, 0, 1, scm_set_lambda);
1044   scm_c_define_gsubr ("set-lambda!", 2, 0, 0, scm_set_lambda_x);
1045   scm_c_define_gsubr ("aisleriot-random", 1, 0, 0, scm_myrandom);
1046   scm_c_define_gsubr ("click-to-move?", 0, 0, 0, scm_click_to_move_p);
1047   scm_c_define_gsubr ("update-score", 1, 0, 0, scm_update_score);
1048   scm_c_define_gsubr ("get-timeout", 0, 0, 0, scm_get_timeout);
1049   scm_c_define_gsubr ("set-timeout!", 1, 0, 0, scm_set_timeout);
1050   scm_c_define_gsubr ("delayed-call", 1, 0, 0, scm_delayed_call);
1051   scm_c_define_gsubr ("undo-set-sensitive", 1, 0, 0, scm_undo_set_sensitive);
1052   scm_c_define_gsubr ("redo-set-sensitive", 1, 0, 0, scm_redo_set_sensitive);
1053   scm_c_define_gsubr ("dealable-set-sensitive", 1, 0, 0, scm_dealable_set_sensitive);
1054 
1055   scm_c_export ("set-feature-word!",
1056                 "get-feature-word",
1057                 "set-statusbar-message-c",
1058                 "reset-surface",
1059                 "add-slot",
1060                 "get-slot",
1061                 "set-cards-c!",
1062                 "set-slot-y-expansion!",
1063                 "set-slot-x-expansion!",
1064                 "set-lambda",
1065                 "set-lambda!",
1066                 "aisleriot-random",
1067                 "click-to-move?",
1068                 "update-score",
1069                 "get-timeout",
1070                 "set-timeout!",
1071                 "delayed-call",
1072                 "undo-set-sensitive",
1073                 "redo-set-sensitive",
1074                 "dealable-set-sensitive",
1075                 NULL);
1076 }
1077 
1078 static void
update_game_dealable(AisleriotGame * game)1079 update_game_dealable (AisleriotGame *game)
1080 {
1081   SCM retval;
1082 
1083   if ((game->features & FEATURE_DEALABLE) == 0)
1084     return;
1085 
1086   if (!game_scm_call (game->lambdas[DEALABLE_LAMBDA], NULL, 0, &retval))
1087     return;
1088 
1089   set_game_dealable (game, scm_is_true (retval));
1090 }
1091 
1092 static gboolean
cscmi_game_over_lambda(void)1093 cscmi_game_over_lambda (void)
1094 {
1095   AisleriotGame *game = app_game;
1096   SCM retval;
1097 
1098   if (!game_scm_call (game->lambdas[GAME_OVER_LAMBDA], NULL, 0, &retval))
1099     return TRUE;
1100 
1101   return scm_is_true (retval);
1102 }
1103 
1104 static gboolean
cscmi_winning_game_lambda(void)1105 cscmi_winning_game_lambda (void)
1106 {
1107   AisleriotGame *game = app_game;
1108   SCM retval;
1109 
1110   if (!game_scm_call (game->lambdas[WINNING_GAME_LAMBDA], NULL, 0, &retval))
1111     return FALSE;
1112 
1113   return scm_is_true (retval);
1114 }
1115 
1116 /* Class implementation */
1117 
1118 static void
aisleriot_game_init(AisleriotGame * game)1119 aisleriot_game_init (AisleriotGame *game)
1120 {
1121   game->rand = game->saved_rand = NULL;
1122 
1123   game->state = GAME_UNINITIALISED;
1124 
1125   game->slots = g_ptr_array_sized_new (SLOT_CARDS_N_PREALLOC);
1126 
1127   game->timer = g_timer_new ();
1128 
1129   game->timeout = 60 * 60;
1130   game->score = NULL;
1131 }
1132 
1133 static GObject *
aisleriot_game_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_params)1134 aisleriot_game_constructor (GType type,
1135                             guint n_construct_properties,
1136                             GObjectConstructParam *construct_params)
1137 {
1138   GObject *object;
1139 
1140   g_assert (app_game == NULL);
1141 
1142   object = G_OBJECT_CLASS (aisleriot_game_parent_class)->constructor
1143              (type, n_construct_properties, construct_params);
1144 
1145   app_game = AISLERIOT_GAME (object);
1146 
1147   return object;
1148 }
1149 
1150 static void
aisleriot_game_finalize(GObject * object)1151 aisleriot_game_finalize (GObject *object)
1152 {
1153   AisleriotGame *game = AISLERIOT_GAME (object);
1154 
1155   /* Update the statistics */
1156   /* FIXMEchpe unless we're saving to session and exiting because of session logout! */
1157   if (game->state >= GAME_RUNNING) {
1158     update_statistics (game);
1159   }
1160 
1161   clear_delayed_call (game);
1162   clear_slots (game, FALSE);
1163   g_ptr_array_free (game->slots, TRUE);
1164 
1165   g_free (game->game_module);
1166 
1167   g_timer_destroy (game->timer);
1168 
1169   if (game->rand)
1170     g_rand_free (game->rand);
1171   if (game->saved_rand)
1172     g_rand_free (game->saved_rand);
1173 
1174   free (game->score);
1175 
1176   app_game = NULL;
1177 
1178   G_OBJECT_CLASS (aisleriot_game_parent_class)->finalize (object);
1179 }
1180 
1181 static void
aisleriot_game_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1182 aisleriot_game_get_property (GObject *object,
1183 			     guint prop_id,
1184 			     GValue *value,
1185 			     GParamSpec *pspec)
1186 {
1187   AisleriotGame *game = AISLERIOT_GAME (object);
1188 
1189   switch (prop_id) {
1190     case PROP_CAN_UNDO:
1191       g_value_set_boolean (value, game->can_undo);
1192       break;
1193     case PROP_CAN_REDO:
1194       g_value_set_boolean (value, game->can_redo);
1195       break;
1196     case PROP_CAN_DEAL:
1197       g_value_set_boolean (value, game->can_deal);
1198       break;
1199     case PROP_GAME_FILE:
1200       g_value_set_string (value, game->game_module);
1201       break;
1202     case PROP_SCORE:
1203       g_value_set_string (value, game->score);
1204       break;
1205     default:
1206       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1207       break;
1208   }
1209 }
1210 
1211 static void
aisleriot_game_class_init(AisleriotGameClass * klass)1212 aisleriot_game_class_init (AisleriotGameClass *klass)
1213 {
1214   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1215   GType param_types[] = { G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE };
1216   GType error_types[] = { G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE };
1217   GType ptr_types[] = { G_TYPE_POINTER };
1218   SCM variable;
1219   char *path;
1220 
1221   gobject_class->constructor = aisleriot_game_constructor;
1222   gobject_class->finalize = aisleriot_game_finalize;
1223   gobject_class->get_property = aisleriot_game_get_property;
1224 
1225   signals[GAME_TYPE] =
1226     g_signal_newv (I_("game-type"),
1227                    G_OBJECT_CLASS_TYPE (gobject_class),
1228                    (GSignalFlags) (G_SIGNAL_RUN_LAST),
1229                    NULL,
1230                    NULL, NULL,
1231                    g_cclosure_marshal_VOID__VOID,
1232                    G_TYPE_NONE,
1233                    0, NULL);
1234 
1235   signals[GAME_CLEARED] =
1236     g_signal_newv (I_("game-cleared"),
1237                    G_OBJECT_CLASS_TYPE (gobject_class),
1238                    (GSignalFlags) (G_SIGNAL_RUN_LAST),
1239                    NULL,
1240                    NULL, NULL,
1241                    g_cclosure_marshal_VOID__VOID,
1242                    G_TYPE_NONE,
1243                    0, NULL);
1244 
1245   signals[GAME_NEW] =
1246     g_signal_newv (I_("game-new"),
1247                    G_OBJECT_CLASS_TYPE (gobject_class),
1248                    (GSignalFlags) (G_SIGNAL_RUN_LAST),
1249                    NULL,
1250                    NULL, NULL,
1251                    g_cclosure_marshal_VOID__VOID,
1252                    G_TYPE_NONE,
1253                    0, NULL);
1254 
1255   signals[SLOT_CHANGED] =
1256     g_signal_newv (I_("slot-changed"),
1257                    G_OBJECT_CLASS_TYPE (gobject_class),
1258                    (GSignalFlags) (G_SIGNAL_RUN_LAST),
1259                    NULL,
1260                    NULL, NULL,
1261                    g_cclosure_marshal_VOID__POINTER,
1262                    G_TYPE_NONE,
1263                    1, ptr_types);
1264 
1265  signals[GAME_MESSAGE] =
1266     g_signal_newv (I_("message"),
1267                    G_OBJECT_CLASS_TYPE (gobject_class),
1268                    (GSignalFlags) (G_SIGNAL_RUN_LAST),
1269                    NULL,
1270                    NULL, NULL,
1271                    g_cclosure_marshal_VOID__STRING,
1272                    G_TYPE_NONE,
1273                    1, param_types);
1274 
1275  signals[EXCEPTION] =
1276     g_signal_newv (I_("exception"),
1277                    G_OBJECT_CLASS_TYPE (gobject_class),
1278                    (GSignalFlags) (G_SIGNAL_RUN_LAST),
1279                    NULL,
1280                    NULL, NULL,
1281                    g_cclosure_marshal_VOID__BOXED,
1282                    G_TYPE_NONE,
1283                    1, error_types);
1284 
1285   g_object_class_install_property
1286     (gobject_class,
1287      PROP_CAN_UNDO,
1288      g_param_spec_boolean ("can-undo", NULL, NULL,
1289                            FALSE,
1290                            G_PARAM_READABLE |
1291                            G_PARAM_STATIC_STRINGS));
1292 
1293   g_object_class_install_property
1294     (gobject_class,
1295      PROP_CAN_REDO,
1296      g_param_spec_boolean ("can-redo", NULL, NULL,
1297                            FALSE,
1298                            G_PARAM_READABLE |
1299                            G_PARAM_STATIC_STRINGS));
1300 
1301   g_object_class_install_property
1302     (gobject_class,
1303      PROP_CAN_DEAL,
1304      g_param_spec_boolean ("can-deal", NULL, NULL,
1305                            FALSE,
1306                            G_PARAM_READABLE |
1307                            G_PARAM_STATIC_STRINGS));
1308 
1309   g_object_class_install_property
1310     (gobject_class,
1311      PROP_GAME_FILE,
1312      g_param_spec_string ("game-file", NULL, NULL,
1313                           NULL,
1314                           G_PARAM_READABLE |
1315                           G_PARAM_STATIC_STRINGS));
1316 
1317   g_object_class_install_property
1318     (gobject_class,
1319      PROP_SCORE,
1320      g_param_spec_string ("score", NULL, NULL,
1321                           NULL,
1322                           G_PARAM_READABLE |
1323                           G_PARAM_STATIC_STRINGS));
1324 
1325   g_object_class_install_property
1326     (gobject_class,
1327      PROP_STATE,
1328      g_param_spec_uint ("state", NULL, NULL,
1329                         0, LAST_GAME_STATE, 0,
1330                         G_PARAM_READABLE |
1331                         G_PARAM_STATIC_STRINGS));
1332 
1333   scm_c_define_module ("aisleriot interface", cscm_init, NULL);
1334 
1335   /* Append load-path */
1336   path = g_build_filename (ar_runtime_get_directory (AR_RUNTIME_PKG_DATA_DIRECTORY),
1337                            "guile",
1338                            SCM_EFFECTIVE_VERSION,
1339                            NULL);
1340   variable = scm_c_module_lookup (scm_the_root_module (), "%load-path");
1341   scm_variable_set_x (variable, scm_append_x (scm_list_2 (scm_variable_ref (variable),
1342                                                           scm_list_1 (scm_from_utf8_string (path)))));
1343   g_free (path);
1344 
1345   path = g_build_filename (ar_runtime_get_directory (AR_RUNTIME_PKG_LIBRARY_DIRECTORY),
1346                            "guile",
1347                            SCM_EFFECTIVE_VERSION,
1348                            NULL);
1349   variable = scm_c_module_lookup (scm_the_root_module (), "%load-compiled-path");
1350   scm_variable_set_x (variable, scm_append_x (scm_list_2 (scm_variable_ref (variable),
1351                                                           scm_list_1 (scm_from_utf8_string (path)))));
1352   g_free (path);
1353 }
1354 
1355 /* public API */
1356 
1357 /**
1358  * ar_slot_get_slot_type:
1359  * @slot: a #ArSlot
1360  *
1361  * Returns: the slot type of @slot
1362  */
1363 ArSlotType
ar_slot_get_slot_type(ArSlot * slot)1364 ar_slot_get_slot_type (ArSlot *slot)
1365 {
1366   g_return_val_if_fail (slot != NULL, AR_SLOT_UNKNOWN);
1367 
1368   return slot->type;
1369 }
1370 
1371 /**
1372  * ar_slot_get_type_string:
1373  * @slot: a #ArSlot
1374  *
1375  * Returns: a string describing the slot type
1376  */
1377 const char *
ar_slot_get_type_string(ArSlot * slot)1378 ar_slot_get_type_string (ArSlot *slot)
1379 {
1380   const char *text = NULL;
1381 
1382   g_return_val_if_fail (slot != NULL, NULL);
1383 
1384   switch (slot->type) {
1385     case AR_SLOT_UNKNOWN:
1386       text = NULL;
1387       break;
1388     case AR_SLOT_CHOOSER:
1389       /* Translators: this is the name of a type of card slot */
1390       text = C_("slot type", "chooser");
1391       break;
1392     case AR_SLOT_FOUNDATION:
1393       /* Translators: this is the name of a type of card slot */
1394       text = C_("slot type", "foundation");
1395       break;
1396     case AR_SLOT_RESERVE:
1397       /* Translators: this is the name of a type of card slot */
1398       text = C_("slot type", "reserve");
1399       break;
1400     case AR_SLOT_STOCK:
1401       /* Translators: this is the name of a type of card slot */
1402       text = C_("slot type", "stock");
1403       break;
1404     case AR_SLOT_TABLEAU:
1405       /* Translators: this is the name of a type of card slot */
1406       text = C_("slot type", "tableau");
1407       break;
1408     case AR_SLOT_WASTE:
1409       /* Translators: this is the name of a type of card slot */
1410       text = C_("slot type", "waste");
1411       break;
1412   }
1413 
1414   return text;
1415 }
1416 
1417 /**
1418  * ar_slot_get_hint_string:
1419  * @slot: a #ArSlot
1420  *
1421  * Returns: a string describing the slot type
1422  */
1423 char *
ar_slot_get_hint_string(ArSlot * slot,int cardid)1424 ar_slot_get_hint_string (ArSlot *slot,
1425                          int cardid)
1426 {
1427   const char *card_name;
1428 
1429   g_return_val_if_fail (slot != NULL, NULL);
1430 
1431   if (slot->type == AR_SLOT_UNKNOWN)
1432     return NULL;
1433 
1434   if (cardid < 0)
1435     return g_strdup (ar_slot_get_type_string (slot));
1436 
1437   card_name = ar_card_get_locale_name (CARD (slot->cards->data[cardid]));
1438 
1439   switch (slot->type) {
1440     case AR_SLOT_CHOOSER:
1441       /* Translators: %s is the name of the card; "chooser" is the name of a type of card slot */
1442       return g_strdup_printf (C_("slot hint", "%s on chooser"), card_name);
1443 
1444     case AR_SLOT_FOUNDATION:
1445       /* Translators: %s is the name of the card; "foundation" is the name of a type of card slot */
1446       return g_strdup_printf (C_("slot hint", "%s on foundation"), card_name);
1447 
1448     case AR_SLOT_RESERVE:
1449       /* Translators: %s is the name of the card; "reserve" is the name of a type of card slot */
1450       return g_strdup_printf (C_("slot hint", "%s on reserve"), card_name);
1451 
1452     case AR_SLOT_STOCK:
1453       /* Translators: %s is the name of the card; "stock" is the name of a type of card slot */
1454       return g_strdup_printf (C_("slot hint", "%s on stock"), card_name);
1455 
1456     case AR_SLOT_TABLEAU:
1457       /* Translators: %s is the name of the card; "tableau" is the name of a type of card slot */
1458       return g_strdup_printf (C_("slot hint", "%s on tableau"), card_name);
1459 
1460     case AR_SLOT_WASTE:
1461       /* Translators: %s is the name of the card; "waste" is the name of a type of card slot */
1462       return g_strdup_printf (C_("slot hint", "%s on waste"), card_name);
1463 
1464     default:
1465       g_assert_not_reached ();
1466   }
1467 
1468   return NULL;
1469 }
1470 
1471 /**
1472  * aisleriot_game_error_quark:
1473  *
1474  * Returns: the #GQuark used for errors
1475  */
1476 GQuark
aisleriot_game_error_quark(void)1477 aisleriot_game_error_quark (void)
1478 {
1479   static GQuark quark = 0;
1480 
1481   if (G_UNLIKELY (quark == 0)) {
1482     quark = g_quark_from_static_string ("AisleRiotGameError");
1483   }
1484 
1485   return quark;
1486 }
1487 
1488 /**
1489  * aisleriot_game_new:
1490  *
1491  * Returns: a new #AisleriotGame
1492  */
1493 AisleriotGame *
aisleriot_game_new(void)1494 aisleriot_game_new (void)
1495 {
1496   return g_object_new (AISLERIOT_TYPE_GAME, NULL);
1497 }
1498 
1499 /**
1500  * aisleriot_game_get_slots:
1501  * @game:
1502  *
1503  * Returns an array of #Slot.
1504  *
1505  * Returns: a #GPtrArray; it is owned by @game and must not be modified or freed
1506  */
1507 GPtrArray *
aisleriot_game_get_slots(AisleriotGame * game)1508 aisleriot_game_get_slots (AisleriotGame *game)
1509 {
1510   return game->slots;
1511 }
1512 
1513 /**
1514  * aisleriot_game_slot_add_cards:
1515  * @game:
1516  * @slot:
1517  * @cards:
1518  * @n_cards:
1519  *
1520  * Adds @n_cards from @cards to the top of @slot.
1521  */
1522 void
aisleriot_game_slot_add_cards(AisleriotGame * game,ArSlot * slot,guint8 * cards,guint n_cards)1523 aisleriot_game_slot_add_cards (AisleriotGame *game,
1524                                ArSlot *slot,
1525                                guint8 *cards,
1526                                guint n_cards)
1527 {
1528   g_byte_array_append (slot->cards, cards, n_cards);
1529 
1530   g_signal_emit (game, signals[SLOT_CHANGED], 0, slot);
1531 }
1532 
1533 /**
1534  * aisleriot_game_get_state:
1535  * @game:
1536  *
1537  * Returns the current game state.
1538  *
1539  * Returns: a #AisleriotGameState
1540  */
1541 guint
aisleriot_game_get_state(AisleriotGame * game)1542 aisleriot_game_get_state (AisleriotGame *game)
1543 {
1544   return game->state;
1545 }
1546 
1547 /**
1548  * aisleriot_game_get_features:
1549  * @game:
1550  *
1551  * Returns the game features.
1552  *
1553  * Returns: a #AisleriotGameFeatures
1554  */
1555 guint
aisleriot_game_get_features(AisleriotGame * game)1556 aisleriot_game_get_features (AisleriotGame *game)
1557 {
1558   return game->features;
1559 }
1560 
1561 /**
1562  * aisleriot_game_start:
1563  * @game:
1564  *
1565  * Starts the game, if it hasn't started already.
1566  */
1567 void
aisleriot_game_start(AisleriotGame * game)1568 aisleriot_game_start (AisleriotGame *game)
1569 {
1570   if (game->state == GAME_BEGIN) {
1571     set_game_state (game, GAME_RUNNING);
1572   }
1573 }
1574 
1575 /**
1576  * aisleriot_game_set_paused:
1577  * @game:
1578  * @paused: whether to pause the game
1579  *
1580  * Stops or resumes the game timer.
1581  */
1582 void
aisleriot_game_set_paused(AisleriotGame * game,gboolean paused)1583 aisleriot_game_set_paused (AisleriotGame *game,
1584                            gboolean paused)
1585 {
1586   g_return_if_fail (game->state == GAME_RUNNING);
1587 
1588   paused = paused != FALSE;
1589   if (paused == game->paused)
1590     return;
1591 
1592   game->paused = paused;
1593 
1594   if (paused) {
1595     g_timer_stop (game->timer);
1596   } else {
1597     g_timer_continue (game->timer);
1598   }
1599 }
1600 
1601 /**
1602  * aisleriot_game_undo_move:
1603  * @game:
1604  *
1605  * Undoes the last move.
1606  */
1607 void
aisleriot_game_undo_move(AisleriotGame * game)1608 aisleriot_game_undo_move (AisleriotGame *game)
1609 {
1610   /* If the game had ended, reset to RUNNING */
1611   if (game->state >= GAME_OVER) {
1612     set_game_state (game, GAME_RUNNING);
1613   }
1614 
1615   if (!game_scm_call_by_name ("undo", NULL, 0, NULL))
1616     return;
1617 
1618   update_game_dealable (game);
1619 }
1620 
1621 /**
1622  * aisleriot_game_redo_move:
1623  * @game:
1624  *
1625  * Redoes the last undone move.
1626  */
1627 void
aisleriot_game_redo_move(AisleriotGame * game)1628 aisleriot_game_redo_move (AisleriotGame *game)
1629 {
1630   if (!game_scm_call_by_name ("redo", NULL, 0, NULL))
1631     return;
1632 
1633   /* We need this now that you can undo a losing move. */
1634   aisleriot_game_test_end_of_game (game);
1635 }
1636 
1637 static SCM
game_scm_load_game(void * user_data)1638 game_scm_load_game (void *user_data)
1639 {
1640   AisleriotGame *game = app_game;
1641   const char *game_module = user_data;
1642   int i;
1643 
1644   scm_dynwind_begin (0);
1645 
1646   scm_primitive_load_path (scm_from_utf8_string (game_module));
1647 
1648   for (i = 0; i <= LAST_MANDATORY_LAMBDA; ++i) {
1649     if (scm_is_false (scm_procedure_p (game->lambdas[i]))) {
1650       scm_throw (scm_from_locale_symbol ("aisleriot-invalid-lambda"),
1651                  scm_list_3 (scm_from_utf8_string ("Not a procedure"),
1652                              scm_from_int (i),
1653                              game->lambdas[i]));
1654     }
1655   }
1656   if ((game->features & FEATURE_DROPPABLE) &&
1657       scm_is_false (scm_procedure_p (game->lambdas[DROPPABLE_LAMBDA])))
1658     scm_throw (scm_from_locale_symbol ("aisleriot-invalid-lambda"),
1659                scm_list_3 (scm_from_utf8_string ("Not a procedure"),
1660                            scm_from_int (DROPPABLE_LAMBDA),
1661                            game->lambdas[i]));
1662   if ((game->features & FEATURE_DEALABLE) &&
1663       scm_is_false (scm_procedure_p (game->lambdas[DEALABLE_LAMBDA])))
1664     scm_throw (scm_from_locale_symbol ("aisleriot-invalid-lambda"),
1665                scm_list_3 (scm_from_utf8_string ("Not a procedure"),
1666                            scm_from_int (DEALABLE_LAMBDA),
1667                            game->lambdas[i]));
1668 
1669   scm_dynwind_end ();
1670 
1671   return SCM_BOOL_T;
1672 }
1673 
1674 /**
1675  * aisleriot_game_load_game:
1676  * @game:
1677  * @game_module: the game module to load
1678  * @error: a location for a #GError
1679  *
1680  * Loads the game @game_module into @game. If there is an error,
1681  * @error is filled in and %FALSE returned.
1682  *
1683  * Returns: %TRUE iff loading the game succeeded
1684  */
1685 gboolean
aisleriot_game_load_game(AisleriotGame * game,const char * game_module,GError ** error)1686 aisleriot_game_load_game (AisleriotGame *game,
1687                           const char *game_module,
1688                           GError **error)
1689 {
1690   GObject *object = G_OBJECT (game);
1691   GError *err = NULL;
1692   int i;
1693 
1694   g_return_val_if_fail (game_module != NULL && game_module[0] != '\0', FALSE);
1695 
1696   /* FIXMEchpe: allow re-loading */
1697   if (g_strcmp0 (game_module, game->game_module) == 0)
1698     return TRUE;
1699 
1700   /* We use @game_module as a filename, but since it'll be used in configuration
1701    * as a key, we also require it to be valid UTF-8 !
1702    */
1703   if (!g_utf8_validate (game_module, -1, NULL)) {
1704     g_set_error (error, AISLERIOT_GAME_ERROR, GAME_ERROR_GENERIC,
1705                  "Invalid UTF-8");
1706     return FALSE;
1707   }
1708 
1709   /* If we're aborting an old game count it as a failure for
1710    * statistical purposes.
1711    */
1712   if (game->state >= GAME_RUNNING) {
1713     update_statistics (game);
1714   }
1715 
1716   g_object_freeze_notify (object);
1717 
1718   clear_delayed_call (game);
1719   set_game_state (game, GAME_UNINITIALISED);
1720   set_game_undoable (game, FALSE);
1721   set_game_redoable (game, FALSE);
1722   set_game_dealable (game, FALSE);
1723   game->features = 0;
1724   game->had_exception = FALSE;
1725   for (i = 0; i < N_LAMBDAS; ++i)
1726     game->lambdas[i] = SCM_UNDEFINED;
1727   g_free (game->game_module);
1728   game->game_module = g_strdup (game_module);
1729 
1730   scm_c_catch (SCM_BOOL_T,
1731                (scm_t_catch_body) game_scm_load_game, (void *) game_module,
1732                game_scm_catch_handler, NULL,
1733                game_scm_pre_unwind_handler, &err);
1734 
1735   if (err) {
1736     g_propagate_error (error, err);
1737     g_object_thaw_notify (object);
1738     return FALSE;
1739   }
1740 
1741   set_game_state (game, GAME_LOADED);
1742 
1743   g_object_notify (object, "game-file");
1744 
1745   g_object_thaw_notify (object);
1746   g_signal_emit (game, signals[GAME_TYPE], 0);
1747 
1748   return TRUE;
1749 }
1750 
1751 static SCM
game_scm_new_game(void * user_data)1752 game_scm_new_game (void *user_data)
1753 {
1754   AisleriotGame *game = user_data;
1755   gboolean game_over;
1756 
1757   g_assert (game->rand != NULL);
1758 
1759   /* It is possible for some games to not have any moves right from the
1760    * start. If this happens we redeal.
1761    */
1762   /* FIXMEchpe we should have a maximum number of tries, and then bail out! */
1763   do {
1764     SCM size, lambda, over;
1765 
1766     /* Copy RNG state */
1767     if (game->saved_rand)
1768       g_rand_free (game->saved_rand);
1769     game->saved_rand = g_rand_copy (game->rand);
1770 
1771     size = scm_call_0 (game->lambdas[NEW_GAME_LAMBDA]);
1772     game->width = scm_to_double (SCM_CAR (size));
1773     game->height = scm_to_double (SCM_CADR (size));
1774     scm_remember_upto_here_1 (size);
1775 
1776     lambda = scm_c_eval_string ("start-game");
1777     scm_call_0 (lambda);
1778     scm_remember_upto_here_1 (lambda);
1779 
1780     over = scm_call_0 (game->lambdas[GAME_OVER_LAMBDA]);
1781     game_over = scm_is_true (over);
1782     scm_remember_upto_here_1 (over);
1783   } while (!game_over);
1784 
1785   if (game->features & FEATURE_DEALABLE) {
1786     SCM dealable;
1787 
1788     dealable = scm_call_0 (game->lambdas[DEALABLE_LAMBDA]);
1789     set_game_dealable (game, scm_is_true (dealable));
1790     scm_remember_upto_here_1 (dealable);
1791   }
1792 
1793   return SCM_BOOL_T;
1794 }
1795 
1796 /*
1797  * aisleriot_game_new_game_internal:
1798  * @game:
1799  * @rand: (allow-none) (transfer full): a #GRand, or %NULL
1800  * @update_statistics: whether to update statistic
1801  *
1802  * Starts a new game of the currently loaded game type.
1803  *
1804  * @game will take ownership of @rand.
1805  */
1806 static void
aisleriot_game_new_game_internal(AisleriotGame * game,GRand * g_rand,gboolean count_loss)1807 aisleriot_game_new_game_internal (AisleriotGame *game,
1808                                   GRand *g_rand,
1809                                   gboolean count_loss)
1810 {
1811   GObject *object = G_OBJECT (game);
1812   GError *err = NULL;
1813 
1814   g_return_if_fail (game->state > GAME_UNINITIALISED);
1815 
1816   g_object_freeze_notify (object);
1817 
1818   /* Clear exception */
1819   game->had_exception = FALSE;
1820   game->paused = FALSE;
1821 
1822   /* If we're aborting an old game count it as a failure for
1823    * statistical purposes.
1824    * But treat a restart as part of the same game. Eventually either
1825    * the player will win or lose and then it gets counted.
1826    */
1827   /* FIXMEchpe: this allows cheating the statistics by doing
1828    * Restart, then New Game.
1829    */
1830   if (count_loss &&
1831       game->state >= GAME_RUNNING) {
1832     update_statistics (game);
1833   }
1834 
1835   if (g_rand != NULL) {
1836     if (game->rand)
1837       g_rand_free (game->rand);
1838 
1839     game->rand = g_rand; /* adopted */
1840   } else if (game->rand == NULL) {
1841     game->rand = g_rand_new ();
1842   }
1843 
1844   if (game->saved_rand)
1845     g_rand_free (game->saved_rand);
1846   game->saved_rand = NULL;
1847 
1848   clear_delayed_call (game);
1849   /* The game isn't actually in progress until the user makes a move */
1850   set_game_state (game, GAME_BEGIN);
1851   set_game_undoable (game, FALSE);
1852   set_game_redoable (game, FALSE);
1853 
1854   scm_c_catch (SCM_BOOL_T,
1855                (scm_t_catch_body) game_scm_new_game, (void *) game,
1856                game_scm_catch_handler, NULL,
1857                game_scm_pre_unwind_handler, &err);
1858 
1859   if (err) {
1860     game->had_exception = TRUE;
1861 
1862     g_signal_emit (game, signals[EXCEPTION], 0, err);
1863     g_error_free (err);
1864     g_object_thaw_notify (object);
1865     return;
1866   }
1867 
1868   g_object_thaw_notify (object);
1869   g_signal_emit (game, signals[GAME_NEW], 0);
1870 }
1871 
1872 /**
1873  * aisleriot_game_new_game:
1874  * @game:
1875  *
1876  * Starts a new game of the currently loaded game type.
1877  *
1878  * @game will take ownership of @rand.
1879  */
1880 void
aisleriot_game_new_game(AisleriotGame * game)1881 aisleriot_game_new_game (AisleriotGame *game)
1882 {
1883   aisleriot_game_new_game_internal (game, NULL, TRUE);
1884 }
1885 
1886 /**
1887  * aisleriot_game_new_game_with_rand:
1888  * @game:
1889  * @rand: (allow-none) (transfer full): a #GRand, or %NULL
1890  *
1891  * Starts a new game of the currently loaded game type.
1892  *
1893  * @game will take ownership of @rand.
1894  */
1895 void
aisleriot_game_new_game_with_rand(AisleriotGame * game,GRand * g_rand)1896 aisleriot_game_new_game_with_rand (AisleriotGame *game,
1897                                    GRand *g_rand)
1898 {
1899   aisleriot_game_new_game_internal (game, g_rand, TRUE);
1900 }
1901 
1902 /**
1903  * aisleriot_game_restart_game:
1904  * @game:
1905  *
1906  * Restarts the current game from the beginning.
1907  */
1908 void
aisleriot_game_restart_game(AisleriotGame * game)1909 aisleriot_game_restart_game (AisleriotGame *game)
1910 {
1911   GRand *g_rand;
1912 
1913   g_rand = game->saved_rand;
1914   game->saved_rand = NULL;
1915 
1916   aisleriot_game_new_game_internal (game, g_rand, FALSE);
1917 }
1918 
1919 /**
1920  * aisleriot_game_get_game_module:
1921  * @game:
1922  *
1923  * Returns the game filename for the currently loaded game type.
1924  *
1925  * Returns: a string owned by @game; you must not modify or free it
1926  */
1927 const char *
aisleriot_game_get_game_module(AisleriotGame * game)1928 aisleriot_game_get_game_module (AisleriotGame *game)
1929 {
1930   return game->game_module;
1931 }
1932 
1933 /**
1934  * aisleriot_game_get_name:
1935  * @game:
1936  *
1937  * Returns the name of the currently loaded game type in a form
1938  * suitable for presentation to the user.
1939  *
1940  * Returns: a newly allocated string
1941  */
1942 char *
aisleriot_game_get_name(AisleriotGame * game)1943 aisleriot_game_get_name (AisleriotGame *game)
1944 {
1945   return ar_filename_to_display_name (game->game_module);
1946 }
1947 
1948 /**
1949  * aisleriot_game_get_game_module:
1950  * @game:
1951  * @width: a location to store the width in
1952  * @height: a location to store the height in
1953  *
1954  * Returns the board size to use for the game, in game coordinates.
1955  */
1956 void
aisleriot_game_get_geometry(AisleriotGame * game,double * width,double * height)1957 aisleriot_game_get_geometry (AisleriotGame *game,
1958                              double *width,
1959                              double *height)
1960 {
1961   *width = game->width;
1962   *height = game->height;
1963 }
1964 
1965 /**
1966  * aisleriot_game_drag_valid:
1967  * @game:
1968  * @slot_id:
1969  * @cards:
1970  *
1971  * Checks whether the cards @cards can be moved off of slot @slot.
1972  */
1973 gboolean
aisleriot_game_drag_valid(AisleriotGame * game,int slot_id,guint8 * cards,guint n_cards)1974 aisleriot_game_drag_valid (AisleriotGame *game,
1975                            int slot_id,
1976                            guint8 *cards,
1977                            guint n_cards)
1978 {
1979   SCM retval;
1980   SCM args[2];
1981 
1982   args[0] = scm_from_int (slot_id);
1983   args[1] = c2scm_deck (cards, n_cards);
1984 
1985   if (!game_scm_call (game->lambdas[BUTTON_PRESSED_LAMBDA], args, 2, &retval))
1986     return FALSE;
1987 
1988   scm_remember_upto_here_2 (args[0], args[1]);
1989 
1990   return scm_is_true (retval);
1991 }
1992 
1993 /**
1994  * aisleriot_game_drop_valid:
1995  * @game:
1996  * @start_slot:
1997  * @end_slot:
1998  * @cards:
1999  *
2000  * Checks whether the game allows moving cards @cards from @start_slot
2001  * to @end_slot.
2002  */
2003 gboolean
aisleriot_game_drop_valid(AisleriotGame * game,int start_slot,int end_slot,guint8 * cards,guint n_cards)2004 aisleriot_game_drop_valid (AisleriotGame *game,
2005                            int start_slot,
2006                            int end_slot,
2007                            guint8 *cards,
2008                            guint n_cards)
2009 {
2010   SCM retval;
2011   SCM args[3];
2012 
2013   if ((game->features & FEATURE_DROPPABLE) == 0)
2014     return FALSE;
2015 
2016   args[0] = scm_from_int (start_slot);
2017   args[1] = c2scm_deck (cards, n_cards);
2018   args[2] = scm_from_int (end_slot);
2019   if (!game_scm_call (game->lambdas[DROPPABLE_LAMBDA], args, 3, &retval))
2020     return FALSE;
2021 
2022   scm_remember_upto_here (args[0], args[1], args[2]);
2023 
2024   return scm_is_true (retval);
2025 }
2026 
2027 /**
2028  * aisleriot_game_drop_cards:
2029  * @game:
2030  * @start_slot:
2031  * @end_slot:
2032  * @cards:
2033  *
2034  * Moves cards @cards from @start_slot to @end_slot.
2035  */
2036 gboolean
aisleriot_game_drop_cards(AisleriotGame * game,int start_slot,int end_slot,guint8 * cards,guint n_cards)2037 aisleriot_game_drop_cards (AisleriotGame *game,
2038                            int start_slot,
2039                            int end_slot,
2040                            guint8 *cards,
2041                            guint n_cards)
2042 {
2043   SCM retval;
2044   SCM args[3];
2045 
2046   args[0] = scm_from_int (start_slot);
2047   args[1] = c2scm_deck (cards, n_cards);
2048   args[2] = scm_from_int (end_slot);
2049   if (!game_scm_call (game->lambdas[BUTTON_RELEASED_LAMBDA], args, 3, &retval))
2050     return FALSE;
2051 
2052   scm_remember_upto_here (args[0], args[1], args[2]);
2053 
2054   return scm_is_true (retval);
2055 }
2056 
2057 /**
2058  * aisleriot_game_button_clicked_lambda:
2059  * @game:
2060  * @slot_id:
2061  *
2062  * Performs the "click" action on @slot.
2063  *
2064  * Returns: %TRUE iff any action was taken
2065  */
2066 gboolean
aisleriot_game_button_clicked_lambda(AisleriotGame * game,int slot_id)2067 aisleriot_game_button_clicked_lambda (AisleriotGame *game,
2068                                       int slot_id)
2069 {
2070   SCM retval;
2071   SCM args[1];
2072 
2073   args[0] = scm_from_int (slot_id);
2074   if (!game_scm_call (game->lambdas[BUTTON_CLICKED_LAMBDA], args, 1, &retval))
2075     return FALSE;
2076 
2077   scm_remember_upto_here_1 (args[0]);
2078 
2079   return scm_is_true (retval);
2080 }
2081 
2082 /**
2083  * aisleriot_game_button_double_clicked_lambda:
2084  * @game:
2085  * @slot_id:
2086  *
2087  * Performs the "double click" action on @slot.
2088  *
2089  * Returns: %TRUE iff any action was taken
2090  */
2091 gboolean
aisleriot_game_button_double_clicked_lambda(AisleriotGame * game,int slot_id)2092 aisleriot_game_button_double_clicked_lambda (AisleriotGame *game,
2093                                              int slot_id)
2094 {
2095   SCM retval;
2096   SCM args[1];
2097 
2098   args[0] = scm_from_int (slot_id);
2099   if (!game_scm_call (game->lambdas[BUTTON_DOUBLE_CLICKED_LAMBDA], args, 1, &retval))
2100     return FALSE;
2101 
2102   scm_remember_upto_here_1 (args[0]);
2103 
2104   return scm_is_true (retval);
2105 }
2106 
2107 /**
2108  * aisleriot_game_get_hint:
2109  * @game:
2110  *
2111  * Gets a hint.
2112  *
2113  * Returns: a newly allocated string containing the hint message
2114  */
2115 char *
aisleriot_game_get_hint(AisleriotGame * game)2116 aisleriot_game_get_hint (AisleriotGame *game)
2117 {
2118   SCM hint;
2119   SCM string1, string2;
2120   char *message = NULL;
2121   char *str1, *str2;
2122 
2123   if (!game_scm_call (game->lambdas[HINT_LAMBDA], NULL, 0, &hint))
2124     return NULL;
2125 
2126   scm_dynwind_begin (0);
2127 
2128   if (scm_is_false (hint)) {
2129     message = g_strdup (_("This game does not have hint support yet."));
2130   } else {
2131     switch (scm_to_int (SCM_CAR (hint))) {
2132 
2133     case 0:
2134       string1 = SCM_CADR (hint);
2135       if (!scm_is_string (string1))
2136         break;
2137 
2138       str1 = scm_to_utf8_string (string1);
2139       scm_dynwind_free (str1);
2140       if (!str1)
2141         break;
2142 
2143       message = g_strdup (str1);
2144       break;
2145 
2146     case 1:
2147       string1 = SCM_CADR (hint);
2148       string2 = SCM_CADDR (hint);
2149 
2150       if (!scm_is_string (string1) || !scm_is_string (string2))
2151         break;
2152 
2153       str1 = scm_to_utf8_string (string1);
2154       scm_dynwind_free (str1);
2155       if (!str1)
2156         break;
2157 
2158       str2 = scm_to_utf8_string (string2);
2159       scm_dynwind_free (str2);
2160       if (!str2)
2161         break;
2162 
2163       /* Both %s are card names */
2164       message = g_strdup_printf (_("Move %s onto %s."), str1, str2);
2165       break;
2166 
2167     case 2:
2168       /* NOTE! This case is exactly the same as case 1, but the strings
2169         * are different: the first is a card name, the 2nd a sentence fragment.
2170         * NOTE! FIXMEchpe! This is bad for i18n.
2171         */
2172       string1 = SCM_CADR (hint);
2173       string2 = SCM_CADDR (hint);
2174 
2175       if (!scm_is_string (string1) || !scm_is_string (string2))
2176         break;
2177 
2178       str1 = scm_to_utf8_string (string1);
2179       scm_dynwind_free (str1);
2180       if (!str1)
2181         break;
2182       str2 = scm_to_utf8_string (string2);
2183       scm_dynwind_free (str2);
2184       if (!str2)
2185         break;
2186 
2187       /* The first %s is a card name, the 2nd %s a sentence fragment.
2188         * Yes, we know this is bad for i18n.
2189         */
2190       message = g_strdup_printf (_("Move %s onto %s."), str1, str2);
2191       break;
2192 
2193     case 3: /* This is deprecated (due to i18n issues) do not use. */
2194       g_warning ("This game uses a deprecated hint method (case 3).\n"
2195                  "Please file a bug at http://bugzilla.gnome.org "
2196                  "including this message and the name of the game "
2197                  "you were playing, which is %s.\n",
2198                  aisleriot_game_get_game_module (game));
2199       break;
2200 
2201     case 4: /* This is deprecated (due to i18n issues) do not use. */
2202       g_warning ("This game uses a deprecated hint method (case 4).\n"
2203                  "Please file a bug at http://bugzilla.gnome.org "
2204                  "including this message and the name of the game "
2205                  "you were playing, which is %s.\n",
2206                  aisleriot_game_get_game_module (game));
2207       break;
2208 
2209     default:
2210       message = g_strdup (_("This game is unable to provide a hint."));
2211       break;
2212     }
2213   }
2214 
2215   scm_dynwind_end ();
2216 
2217   return message;
2218 }
2219 
2220 /**
2221  * aisleriot_game_option_free:
2222  * @option: a #AisleriotGameOption
2223  *
2224  * Frees @options.
2225  */
2226 void
aisleriot_game_option_free(AisleriotGameOption * option)2227 aisleriot_game_option_free (AisleriotGameOption *option)
2228 {
2229   g_return_if_fail (option != NULL);
2230 
2231   g_free (option->display_name);
2232   g_slice_free (AisleriotGameOption, option);
2233 }
2234 
2235 /**
2236  * aisleriot_game_get_options:
2237  * @game:
2238  *
2239  * Returns: a newly allocated list containing newly allocated
2240  * #AisleriotGameOption structs.
2241  */
2242 GList *
aisleriot_game_get_options(AisleriotGame * game)2243 aisleriot_game_get_options (AisleriotGame *game)
2244 {
2245   SCM options_list;
2246   int l, i;
2247   guint32 bit = 1;
2248   AisleriotGameOptionType type = AISLERIOT_GAME_OPTION_CHECK;
2249   GList *options = NULL;
2250 
2251   if (!game_scm_call (game->lambdas[GET_OPTIONS_LAMBDA], NULL, 0, &options_list))
2252     return NULL;
2253 
2254   if (scm_is_false (scm_list_p (options_list)))
2255     return NULL;
2256 
2257   scm_dynwind_begin (0);
2258 
2259   l = scm_to_int (scm_length (options_list));
2260   bit = 1;
2261   for (i = 0; i < l; i++) {
2262     SCM entry;
2263 
2264     /* Each entry in the options list is a list consisting of a name and
2265      * a variable.
2266      */
2267     entry = scm_list_ref (options_list, scm_from_int (i));
2268     if (!scm_is_false (scm_list_p (entry))) {
2269       SCM entryname;
2270       char *entrynamestr;
2271       gboolean entrystate;
2272       AisleriotGameOption *option;
2273 
2274       entryname = scm_list_ref (entry, scm_from_uint (0));
2275       if (!scm_is_string (entryname))
2276         continue; /* Shouldn't happen */
2277 
2278       entrynamestr = scm_to_utf8_string (entryname);
2279       scm_dynwind_free (entrynamestr);
2280       if (!entrynamestr)
2281         continue;
2282 
2283       entrystate = scm_is_true (scm_list_ref (entry, scm_from_uint (1)));
2284 
2285       option = g_slice_new (AisleriotGameOption);
2286       option->display_name = g_strdup (entrynamestr);
2287       option->type = type;
2288       option->value = bit;
2289       option->set = entrystate != FALSE;
2290 
2291       options = g_list_prepend (options, option);
2292 
2293       bit <<= 1;
2294     } else {
2295       /* If we encounter an atom, change the mode. What the atom is doesn't
2296       * really matter. */
2297       if (type == AISLERIOT_GAME_OPTION_CHECK) {
2298         type = AISLERIOT_GAME_OPTION_RADIO;
2299       } else {
2300         type = AISLERIOT_GAME_OPTION_CHECK;
2301       }
2302     }
2303   }
2304 
2305   scm_dynwind_end ();
2306 
2307   return g_list_reverse (options);
2308 }
2309 
2310 /**
2311  * aisleriot_game_apply_options_lambda:
2312  * @game:
2313  * @changed_mask:
2314  * @changed_value:
2315  *
2316  * Applies options.
2317  *
2318  * Returns: the new options value
2319  */
2320 guint32
aisleriot_game_change_options(AisleriotGame * game,guint32 changed_mask,guint32 changed_value)2321 aisleriot_game_change_options (AisleriotGame *game,
2322                                guint32 changed_mask,
2323                                guint32 changed_value)
2324 {
2325   SCM options_list;
2326   guint32 bit, value;
2327   int l, i;
2328 
2329   if (!game_scm_call (game->lambdas[GET_OPTIONS_LAMBDA], NULL, 0, &options_list))
2330     return 0;
2331 
2332   if (scm_is_false (scm_list_p (options_list)))
2333     return 0;
2334 
2335   value = 0;
2336   bit = 1;
2337   l = scm_to_int (scm_length (options_list));
2338   for (i = 0; i < l; i++) {
2339     SCM entry;
2340 
2341     entry = scm_list_ref (options_list, scm_from_uint (i));
2342     if (scm_is_false (scm_list_p (entry)))
2343       continue;
2344 
2345     if (changed_mask & bit)
2346       scm_list_set_x (entry, scm_from_uint (1), (changed_value & bit) ? SCM_BOOL_T : SCM_BOOL_F);
2347 
2348     if (scm_is_true (scm_list_ref (entry, scm_from_uint (1))))
2349       value |= bit;
2350 
2351     bit <<= 1;
2352   }
2353 
2354   game_scm_call (game->lambdas[APPLY_OPTIONS_LAMBDA], &options_list, 1, NULL);
2355 
2356   scm_remember_upto_here_1 (options_list);
2357   return value;
2358 }
2359 
2360 /**
2361  * aisleriot_game_timeout_lambda:
2362  * @game:
2363  *
2364  * Checks whether @game has timed out
2365  *
2366  * Returns: %TRUE iff the game has timed out
2367  */
2368 gboolean
aisleriot_game_timeout_lambda(AisleriotGame * game)2369 aisleriot_game_timeout_lambda (AisleriotGame *game)
2370 {
2371   SCM retval;
2372 
2373   if (game_scm_call (game->lambdas[TIMEOUT_LAMBDA], NULL, 0, &retval))
2374     return FALSE;
2375 
2376   return scm_is_true (retval);
2377 }
2378 
2379 /**
2380  * aisleriot_game_record_move:
2381  * @game:
2382  * @slot_id:
2383  * @cards:
2384  * @n_cards:
2385  *
2386  * Records the state of @slot and the and cards on it.
2387  */
2388 void
aisleriot_game_record_move(AisleriotGame * game,int slot_id,guint8 * cards,guint n_cards)2389 aisleriot_game_record_move (AisleriotGame *game,
2390                             int slot_id,
2391                             guint8 *cards,
2392                             guint n_cards)
2393 {
2394   SCM args[2];
2395 
2396   args[0] = scm_from_int (slot_id);
2397   args[1] = c2scm_deck (cards, n_cards);
2398 
2399   if (!game_scm_call_by_name ("record-move", args, 2, NULL))
2400     return;
2401 
2402   scm_remember_upto_here_2 (args[0], args[1]);
2403 }
2404 
2405 /**
2406  * aisleriot_game_end_move:
2407  * @game:
2408  *
2409  * Adds the state information stored with aisleriot_game_record_move
2410  * to the undo history.
2411  */
2412 void
aisleriot_game_end_move(AisleriotGame * game)2413 aisleriot_game_end_move (AisleriotGame *game)
2414 {
2415   if (!game_scm_call_by_name ("end-move", NULL, 0, NULL))
2416     return;
2417 }
2418 
2419 /**
2420  * aisleriot_game_discard_move:
2421  * @game:
2422  *
2423  * Discards the state information stored with aisleriot_game_record_move.
2424  */
2425 void
aisleriot_game_discard_move(AisleriotGame * game)2426 aisleriot_game_discard_move (AisleriotGame *game)
2427 {
2428   if (!game_scm_call_by_name ("discard-move", NULL, 0, NULL))
2429     return;
2430 }
2431 
2432 /**
2433  * aisleriot_game_update_game_state:
2434  * @game:
2435  *
2436  * Tests whether the game is over.
2437  */
2438 void
aisleriot_game_test_end_of_game(AisleriotGame * game)2439 aisleriot_game_test_end_of_game (AisleriotGame *game)
2440 {
2441   aisleriot_game_end_move (game);
2442 
2443   update_game_dealable (game);
2444 
2445   if (game->state < GAME_OVER) {
2446     if (!cscmi_game_over_lambda ()) {
2447       guint new_state;
2448 
2449       if (cscmi_winning_game_lambda ()) {
2450         new_state = GAME_WON;
2451       } else {
2452         new_state = GAME_OVER;
2453       }
2454 
2455       set_game_state (game, new_state);
2456 
2457       /* Don't update the statistics here; we'll do that when
2458        * starting the next game (or on finalize).
2459        */
2460     }
2461   }
2462 }
2463 
2464 /**
2465  * aisleriot_game_set_click_to_move:
2466  * @game:
2467  *
2468  * Sets whether the game is using clicks to move, or drag-and-drop.
2469  */
2470 void
aisleriot_game_set_click_to_move(AisleriotGame * game,gboolean enabled)2471 aisleriot_game_set_click_to_move (AisleriotGame *game,
2472                                   gboolean enabled)
2473 {
2474   game->click_to_move = enabled != FALSE;
2475 }
2476 
2477 /**
2478  * aisleriot_game_generate_exception:
2479  * @game:
2480  *
2481  * Generates an artificial scheme exception.
2482  */
2483 void
aisleriot_game_generate_exception(AisleriotGame * game)2484 aisleriot_game_generate_exception (AisleriotGame *game)
2485 {
2486   GError *error = NULL;
2487 
2488   scm_c_catch (SCM_BOOL_T,
2489                (scm_t_catch_body) scm_c_eval_string, (void *) "(/ 1 0)",
2490                game_scm_catch_handler, NULL,
2491                game_scm_pre_unwind_handler, &error);
2492 
2493   if (error) {
2494     g_signal_emit (app_game, signals[EXCEPTION], 0, error);
2495     g_error_free (error);
2496   }
2497 }
2498 
2499 /**
2500  * aisleriot_game_deal_cards:
2501  * @game:
2502  *
2503  * Deals the next card or cards, if possible.
2504  *
2505  * Returns: %TRUE iff cards were dealt
2506  */
2507 void
aisleriot_game_deal_cards(AisleriotGame * game)2508 aisleriot_game_deal_cards (AisleriotGame *game)
2509 {
2510   /* If the game hasn't started yet, start it now */
2511   aisleriot_game_start (game);
2512 
2513   aisleriot_game_record_move (game, -1, NULL, 0);
2514 
2515   if (!game_scm_call_by_name ("do-deal-next-cards", NULL, 0, NULL))
2516     return;
2517 
2518   aisleriot_game_end_move (game);
2519   aisleriot_game_test_end_of_game (game);
2520 }
2521 
2522 /**
2523  * aisleriot_game_get_score:
2524  * @game:
2525  *
2526  * Returns: (transfer none): the current score as a string
2527  */
2528 const char *
aisleriot_game_get_score(AisleriotGame * game)2529 aisleriot_game_get_score (AisleriotGame *game)
2530 {
2531   g_return_val_if_fail (AISLERIOT_IS_GAME (game), NULL);
2532 
2533   return game->score ? game->score : "";
2534 }
2535 
2536 static void
append_games_from_path(GHashTable * hash_table,const char * path,const char * suffix)2537 append_games_from_path (GHashTable *hash_table,
2538                         const char *path,
2539                         const char *suffix)
2540 {
2541   GDir *dir;
2542   const char *filename;
2543 
2544   dir = g_dir_open (path, 0, NULL);
2545   if (dir == NULL)
2546     return;
2547 
2548   while ((filename = g_dir_read_name (dir)) != NULL) {
2549     if (!g_str_has_suffix (filename, suffix))
2550       continue;
2551 
2552     g_hash_table_insert (hash_table,
2553                          ar_filename_to_game_module (filename),
2554                          NULL);
2555   }
2556 }
2557 
2558 static int
compare(gconstpointer * a,gconstpointer * b)2559 compare (gconstpointer *a,
2560          gconstpointer *b)
2561 {
2562   return strcmp ((char *) *a, (char *) *b);
2563 }
2564 
2565 /**
2566  * ar_get_game_modules:
2567  *
2568  * Returns: (tranfer full): the list of available games
2569  */
2570 char **
ar_get_game_modules(void)2571 ar_get_game_modules (void)
2572 {
2573   GHashTable *hash_table;
2574   char *path;
2575   GPtrArray *array;
2576   GHashTableIter iter;
2577   gpointer key;
2578 
2579   hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2580 
2581   path = g_build_filename (ar_runtime_get_directory (AR_RUNTIME_PKG_LIBRARY_DIRECTORY),
2582                            "guile",
2583                            SCM_EFFECTIVE_VERSION,
2584                            NULL);
2585   append_games_from_path (hash_table, path, ".go");
2586   g_free (path);
2587 
2588   path = g_build_filename (ar_runtime_get_directory (AR_RUNTIME_PKG_DATA_DIRECTORY),
2589                            "guile",
2590                            SCM_EFFECTIVE_VERSION,
2591                            NULL);
2592   append_games_from_path (hash_table, path, ".scm");
2593   g_free (path);
2594 
2595   array = g_ptr_array_sized_new (g_hash_table_size (hash_table));
2596 
2597   g_hash_table_iter_init (&iter, hash_table);
2598   while (g_hash_table_iter_next (&iter, &key, NULL)) {
2599     g_hash_table_iter_steal (&iter);
2600     g_ptr_array_add (array, key);
2601   }
2602   g_hash_table_unref (hash_table);
2603 
2604   g_ptr_array_sort (array, (GCompareFunc) compare);
2605   g_ptr_array_add (array, NULL);
2606 
2607   return (char **) g_ptr_array_free (array, FALSE);
2608 }
2609