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, ¤t_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, ¤t_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