1
/* gcompris - wordsgame.c
3
* Copyright (C) 2000 Bruno Coudoin
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.
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.
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/>.
21
#include "gcompris/gcompris.h"
22
#include "gcompris/gameutil.h"
25
#define SOUNDLISTFILE PACKAGE
26
#define MAXWORDSLENGTH 50
27
static GcomprisWordlist *gc_wordlist = NULL;
28
static gboolean board_paused = TRUE;
30
GStaticRWLock items_lock = G_STATIC_RW_LOCK_INIT;
31
GStaticRWLock items2del_lock = G_STATIC_RW_LOCK_INIT;
35
overword - part of word allready typed
36
count - number of allready typed letters in word
37
pos - pointer to current position in word
38
letter - current expected letter to type
41
GnomeCanvasItem *rootitem;
42
GnomeCanvasItem *overwriteItem;
51
items - array of displayed items
52
items2del - array of items where moved offscreen
53
item_on_focus - item on focus in array items. NULL - not set.
56
static GPtrArray *items=NULL;
57
static GPtrArray *items2del=NULL;
58
static LettersItem *item_on_focus=NULL;
61
static GcomprisBoard *gcomprisBoard = NULL;
63
static gint dummy_id = 0;
64
static gint drop_items_id = 0;
67
static void start_board (GcomprisBoard *agcomprisBoard);
68
static void pause_board (gboolean pause);
69
static void end_board (void);
70
static gboolean is_our_board (GcomprisBoard *gcomprisBoard);
71
static void set_level (guint level);
72
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str);
74
static GnomeCanvasItem *wordsgame_create_item(GnomeCanvasGroup *parent);
75
static gint wordsgame_drop_items (GtkWidget *widget, gpointer data);
76
static gint wordsgame_move_items (GtkWidget *widget, gpointer data);
77
static void wordsgame_destroy_item(LettersItem *item);
78
static gboolean wordsgame_destroy_items(GPtrArray *items);
79
static void wordsgame_destroy_all_items(void);
80
static void wordsgame_next_level(void);
81
static void wordsgame_add_new_item(void);
82
static void wordsgame_config_start(GcomprisBoard *agcomprisBoard,
83
GcomprisProfile *aProfile);
84
static void wordsgame_config_stop(void);
87
static void player_win(LettersItem *item);
88
static void player_loose(void);
91
#define MAX_FALLSPEED 7000
93
#define MIN_FALLSPEED 3000
95
#define DEFAULT_FALLSPEED 7000
96
#define DEFAULT_SPEED 150
98
#define INCREMENT_FALLSPEED 1000
99
#define INCREMENT_SPEED 10
102
static guint32 fallSpeed = 0;
103
static double speed = 0.0;
105
static GnomeCanvasItem *preedit_text = NULL;
107
/* Description of this plugin */
108
static BoardPlugin menu_bp =
113
N_("Type the falling words before they reach the ground"),
114
"Bruno Coudoin <bruno.coudoin@free.fr>",
128
wordsgame_config_start,
129
wordsgame_config_stop
133
* Main entry point mandatory for each Gcompris's game
134
* ---------------------------------------------------
138
GET_BPLUGIN_INFO(wordsgame)
141
* in : boolean TRUE = PAUSE : FALSE = UNPAUSE
144
static void pause_board (gboolean pause)
147
if(gcomprisBoard==NULL)
153
g_source_remove (dummy_id);
157
g_source_remove (drop_items_id);
164
drop_items_id = g_timeout_add (0,
165
(GtkFunction) wordsgame_drop_items, NULL);
168
dummy_id = g_timeout_add (10, (GtkFunction) wordsgame_move_items, NULL);
171
board_paused = pause;
176
static void start_board (GcomprisBoard *agcomprisBoard)
179
if(agcomprisBoard!=NULL)
181
gcomprisBoard=agcomprisBoard;
183
/* disable im_context */
184
//gcomprisBoard->disable_im_context = TRUE;
186
gc_set_background(gnome_canvas_root(gcomprisBoard->canvas),
187
"opt/scenery_background.png");
190
gcomprisBoard->level = 1;
191
gcomprisBoard->maxlevel = 6;
192
gcomprisBoard->sublevel = 0;
193
gc_bar_set(GC_BAR_LEVEL|GC_BAR_CONFIG);
197
fallSpeed=DEFAULT_FALLSPEED;
199
gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-$LOCALE.xml");
203
/* Fallback to english */
204
gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-en.xml");
208
gcomprisBoard = NULL;
209
gc_dialog(_("Error: We can't find\na list of words to play this game.\n"), gc_board_end);
214
wordsgame_next_level();
222
if(gcomprisBoard!=NULL)
226
wordsgame_destroy_all_items();
228
gtk_object_destroy(GTK_OBJECT(preedit_text));
232
gcomprisBoard = NULL;
234
if (gc_wordlist != NULL){
235
gc_wordlist_free(gc_wordlist);
243
set_level (guint level)
246
if(gcomprisBoard!=NULL)
248
gcomprisBoard->level=level;
249
wordsgame_next_level();
254
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str)
260
gunichar unichar_letter;
262
if(board_paused && !gcomprisBoard)
266
g_warning("keyval %d", keyval);
272
g_warning("preedit_str %s", preedit_str);
273
/* show the preedit string on bottom of the window */
274
GcomprisProperties *properties = gc_prop_get ();
276
PangoAttrList *attrs;
278
gtk_im_context_get_preedit_string (properties->context,
285
gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
286
gnome_canvas_text_get_type (),
287
"font", gc_skin_font_board_huge_bold,
288
"x", (double) BOARDWIDTH/2,
289
"y", (double) BOARDHEIGHT - 100,
290
"anchor", GTK_ANCHOR_N,
291
//"fill_color_rgba", 0xba00ffff,
295
gnome_canvas_item_set (preedit_text,
305
g_warning("commit_str %s", commit_str);
309
for (i=0; i < g_utf8_strlen(commit_str,-1); i++){
310
unichar_letter = g_utf8_get_char(str);
311
str = g_utf8_next_char(str);
312
if(!g_unichar_isalnum (unichar_letter)){
317
letter = g_new0(gchar,6);
318
g_unichar_to_utf8 (unichar_letter, letter);
320
if(item_on_focus==NULL)
322
g_static_rw_lock_reader_lock (&items_lock);
323
gint count=items->len;
324
g_static_rw_lock_reader_unlock (&items_lock);
326
for (i=0;i<count;i++)
328
g_static_rw_lock_reader_lock (&items_lock);
329
item=g_ptr_array_index(items,i);
330
g_static_rw_lock_reader_unlock (&items_lock);
331
g_assert (item!=NULL);
332
if (strcmp(item->letter,letter)==0)
341
if(item_on_focus!=NULL)
344
if(strcmp(item_on_focus->letter, letter)==0)
347
item_on_focus->count++;
348
g_free(item_on_focus->overword);
349
tmpstr = g_utf8_strndup(item_on_focus->word,
350
item_on_focus->count);
351
/* Add the ZERO WIDTH JOINER to force joined char in Arabic and Hangul
352
* http://en.wikipedia.org/wiki/Zero-width_joiner (UTF-8: e2808d)
354
item_on_focus->overword = g_strdup_printf("%s%c%c%c", tmpstr, 0xe2, 0x80, 0x8d);
356
gnome_canvas_item_set (item_on_focus->overwriteItem,
357
"text", item_on_focus->overword,
361
if (item_on_focus->count<g_utf8_strlen(item_on_focus->word,-1))
363
g_free(item_on_focus->letter);
364
item_on_focus->letter=g_utf8_strndup(item_on_focus->pos,1);
365
item_on_focus->pos=g_utf8_find_next_char(item_on_focus->pos,NULL);
369
player_win(item_on_focus);
375
/* It is a loose : unselect the word and defocus */
376
g_free(item_on_focus->overword);
377
item_on_focus->overword=g_strdup(" ");
378
item_on_focus->count=0;
379
g_free(item_on_focus->letter);
380
item_on_focus->letter=g_utf8_strndup(item_on_focus->word,1);
382
item_on_focus->pos=g_utf8_find_next_char(item_on_focus->word,NULL);
384
gnome_canvas_item_set (item_on_focus->overwriteItem,
385
"text", item_on_focus->overword,
395
/* Anyway kid you clicked on the wrong key */
407
is_our_board (GcomprisBoard *gcomprisBoard)
411
if(g_strcasecmp(gcomprisBoard->type, "wordsgame")==0)
413
/* Set the plugin entry */
414
gcomprisBoard->plugin=&menu_bp;
423
/*-------------------------------------------------------------------------------*/
424
/*-------------------------------------------------------------------------------*/
425
/*-------------------------------------------------------------------------------*/
426
/*-------------------------------------------------------------------------------*/
428
/* set initial values for the next level */
429
static void wordsgame_next_level()
433
gcomprisBoard->number_of_sublevel = 10 +
434
((gcomprisBoard->level-1) * 5);
435
gc_score_start(SCORESTYLE_NOTE,
436
gcomprisBoard->width - 220,
437
gcomprisBoard->height - 50,
438
gcomprisBoard->number_of_sublevel);
440
gc_bar_set_level(gcomprisBoard);
441
gc_score_set(gcomprisBoard->sublevel);
443
wordsgame_destroy_all_items();
446
gtk_object_destroy(GTK_OBJECT(preedit_text));
451
items=g_ptr_array_new();
452
items2del=g_ptr_array_new();
455
/* Increase speed only after 5 levels */
456
if(gcomprisBoard->level>5)
458
gint temp = fallSpeed-gcomprisBoard->level*200;
459
if (temp > MIN_FALLSPEED) fallSpeed=temp;
466
static void wordsgame_move_item(LettersItem *item)
468
double x1, y1, x2, y2;
471
gnome_canvas_item_move(item->rootitem, 0, 2.0);
473
gnome_canvas_item_get_bounds (item->rootitem,
479
if(y1>gcomprisBoard->height) {
481
if (item == item_on_focus)
482
item_on_focus = NULL;
484
g_static_rw_lock_writer_lock (&items_lock);
485
g_ptr_array_remove (items, item);
486
g_static_rw_lock_writer_unlock (&items_lock);
488
g_static_rw_lock_writer_lock (&items2del_lock);
489
g_ptr_array_add (items2del, item);
490
g_static_rw_lock_writer_unlock (&items2del_lock);
492
g_timeout_add (100,(GtkFunction) wordsgame_destroy_items, items2del);
499
* This does the moves of the game items on the play canvas
502
static gint wordsgame_move_items (GtkWidget *widget, gpointer data)
504
g_assert (items!=NULL);
508
for (i=items->len-1;i>=0;i--)
511
g_static_rw_lock_reader_lock (&items_lock);
512
item=g_ptr_array_index(items,i);
513
g_static_rw_lock_reader_unlock (&items_lock);
514
wordsgame_move_item(item);
516
dummy_id = g_timeout_add (speed,(GtkFunction) wordsgame_move_items, NULL);
522
static void wordsgame_destroy_item(LettersItem *item)
525
/* The items are freed by player_win */
526
gtk_object_destroy (GTK_OBJECT(item->rootitem));
528
g_free(item->overword);
529
g_free(item->letter);
533
/* Destroy items that falls out of the canvas */
534
static gboolean wordsgame_destroy_items(GPtrArray *item_list)
538
g_assert(item_list!=NULL);
542
if (item_list==items) {
543
g_static_rw_lock_writer_lock (&items_lock);
544
while (item_list->len>0)
546
item = g_ptr_array_index(item_list,0);
547
g_ptr_array_remove_index_fast(item_list,0);
548
wordsgame_destroy_item(item);
550
g_static_rw_lock_writer_unlock (&items_lock);
553
if (item_list==items2del) {
554
g_static_rw_lock_writer_lock (&items2del_lock);
555
while (item_list->len>0)
557
item = g_ptr_array_index(item_list,0);
558
g_ptr_array_remove_index_fast(item_list,0);
559
wordsgame_destroy_item(item);
561
g_static_rw_lock_writer_unlock (&items2del_lock);
567
/* Destroy all the items */
568
static void wordsgame_destroy_all_items()
573
wordsgame_destroy_items(items);
575
g_ptr_array_free (items, TRUE);
579
if (items2del!=NULL){
580
if(items2del->len > 0) {
581
wordsgame_destroy_items(items2del);
583
g_ptr_array_free (items2del, TRUE);
590
static GnomeCanvasItem *wordsgame_create_item(GnomeCanvasGroup *parent)
593
GnomeCanvasItem *item2;
595
gchar *word = gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level);
596
GtkAnchorType direction_anchor = GTK_ANCHOR_NW;
599
/* Should display the dialog box here */
602
// create and init item
603
item = g_new(LettersItem,1);
605
item->overword=g_strdup("");
607
item->letter=g_utf8_strndup(item->word,1);
608
item->pos=g_utf8_find_next_char(item->word, NULL);
610
if (pango_unichar_direction(g_utf8_get_char(item->word)))
611
direction_anchor = GTK_ANCHOR_NE;
614
gnome_canvas_item_new (parent,
615
gnome_canvas_group_get_type (),
620
/* To 'erase' words, I create 2 times the text item. One is empty now */
621
/* It will be filled each time the user enters the right key */
623
gnome_canvas_item_new (GNOME_CANVAS_GROUP(item->rootitem),
624
gnome_canvas_text_get_type (),
626
"font", gc_skin_font_board_huge_bold,
629
"anchor", direction_anchor,
630
"fill_color_rgba", 0xba00ffff,
633
item->overwriteItem = \
634
gnome_canvas_item_new (GNOME_CANVAS_GROUP(item->rootitem),
635
gnome_canvas_text_get_type (),
636
"text", item->overword,
637
"font", gc_skin_font_board_huge_bold,
640
"anchor", direction_anchor,
641
"fill_color", "blue",
644
/*set right x position */
646
double x1, y1, x2, y2;
649
gnome_canvas_item_get_bounds (item->rootitem,
655
if(direction_anchor == GTK_ANCHOR_NW)
656
gnome_canvas_item_move (item->rootitem,(double) (g_random_int()%(gcomprisBoard->width-(gint)(x2))),(double) 0);
659
double new_x = (double)( g_random_int()%gcomprisBoard->width);
662
gnome_canvas_item_move (item->rootitem, new_x ,(double) 0);
665
g_static_rw_lock_writer_lock (&items_lock);
666
g_ptr_array_add(items, item);
667
g_static_rw_lock_writer_unlock (&items_lock);
669
return (item->rootitem);
672
static void wordsgame_add_new_item()
675
g_assert(gcomprisBoard->canvas!=NULL);
676
wordsgame_create_item(gnome_canvas_root(gcomprisBoard->canvas));
681
* This is called on a low frequency and is used to drop new items
684
static gint wordsgame_drop_items (GtkWidget *widget, gpointer data)
686
gc_sound_play_ogg ("sounds/level.wav", NULL);
687
wordsgame_add_new_item();
688
g_source_remove(drop_items_id);
689
drop_items_id = g_timeout_add (fallSpeed,(GtkFunction) wordsgame_drop_items, NULL);
694
static void player_win(LettersItem *item)
697
gc_sound_play_ogg ("sounds/flip.wav", NULL);
699
g_assert(gcomprisBoard!=NULL);
701
gcomprisBoard->sublevel++;
702
gc_score_set(gcomprisBoard->sublevel);
705
g_static_rw_lock_writer_lock (&items_lock);
706
g_ptr_array_remove(items,item);
707
g_static_rw_lock_writer_unlock (&items_lock);
709
g_static_rw_lock_writer_lock (&items2del_lock);
710
g_ptr_array_add(items2del,item);
711
g_static_rw_lock_writer_unlock (&items2del_lock);
713
gnome_canvas_item_hide(item->rootitem);
714
g_timeout_add (500,(GtkFunction) wordsgame_destroy_items, items2del);
717
if(gcomprisBoard->sublevel > gcomprisBoard->number_of_sublevel)
720
/* Try the next level */
721
gcomprisBoard->level++;
722
gcomprisBoard->sublevel = 0;
723
if(gcomprisBoard->level>gcomprisBoard->maxlevel) { // the current board is finished : bail out
724
gc_bonus_end_display(GC_BOARD_FINISHED_RANDOM);
727
wordsgame_next_level();
728
gc_sound_play_ogg ("sounds/bonus.wav", NULL);
733
/* Drop a new item now to speed up the game */
734
g_static_rw_lock_reader_lock (&items_lock);
735
gint count=items->len;
736
g_static_rw_lock_reader_unlock (&items_lock);
741
if ((fallSpeed-=INCREMENT_FALLSPEED) < MIN_FALLSPEED) fallSpeed+=INCREMENT_FALLSPEED;
743
if ((speed-=INCREMENT_SPEED) < MIN_SPEED) speed+=INCREMENT_SPEED;
746
/* Remove pending new item creation to sync the falls */
747
g_source_remove (drop_items_id);
752
drop_items_id = g_timeout_add (0,
753
(GtkFunction) wordsgame_drop_items,
762
static void player_loose()
764
gc_sound_play_ogg ("sounds/crash.wav", NULL);
767
static void conf_ok(gpointer data)
772
static void wordsgame_config_start(GcomprisBoard *agcomprisBoard, GcomprisProfile *aProfile)
777
gchar *label = g_strdup_printf(_("<b>%s</b> configuration\n for profile <b>%s</b>"),
778
agcomprisBoard->name,
779
aProfile? aProfile->name: "");
780
GcomprisBoardConf *bconf;
781
bconf = gc_board_config_window_display( label,
782
(GcomprisConfCallback )conf_ok);
786
gc_board_config_wordlist(bconf, "wordsgame/default-$LOCALE.xml");
789
static void wordsgame_config_stop(void)