Back to index

gcompris  8.2.2
wordsgame.c
Go to the documentation of this file.
00001 /* gcompris - wordsgame.c
00002  *
00003  * Time-stamp: <2006/08/22 02:23:24 bruno>
00004  *
00005  * Copyright (C) 2000 Bruno Coudoin
00006  *
00007  *   This program is free software; you can redistribute it and/or modify
00008  *   it under the terms of the GNU General Public License as published by
00009  *   the Free Software Foundation; either version 2 of the License, or
00010  *   (at your option) any later version.
00011  *
00012  *   This program is distributed in the hope that it will be useful,
00013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *   GNU General Public License for more details.
00016  *
00017  *   You should have received a copy of the GNU General Public License
00018  *   along with this program; if not, write to the Free Software
00019  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00020  */
00021 
00022 #include <string.h>
00023 
00024 #include "gcompris/gcompris.h"
00025 #include "gcompris/gameutil.h"
00026 
00027 
00028 #define SOUNDLISTFILE PACKAGE
00029 #define MAXWORDSLENGTH 50
00030 static GcomprisWordlist *gc_wordlist = NULL;
00031 
00032 GStaticRWLock items_lock = G_STATIC_RW_LOCK_INIT;
00033 GStaticRWLock items2del_lock = G_STATIC_RW_LOCK_INIT;
00034 
00035 /*
00036   word - word to type
00037   overword - part of word allready typed
00038   count - number of allready typed letters in word
00039   pos - pointer to current position in word
00040   letter - current expected letter to type
00041 */
00042 typedef struct {
00043   GnomeCanvasItem *rootitem;
00044   GnomeCanvasItem *overwriteItem;
00045   gchar *word;
00046   gchar *overword;
00047   gint  count;
00048   gchar *pos;
00049   gchar *letter;
00050 } LettersItem;
00051 
00052 /*
00053   items - array of displayed items
00054   items2del - array of items where moved offscreen
00055   item_on_focus -  item on focus in array items. NULL - not set.
00056 */
00057 
00058 static GPtrArray     *items=NULL;
00059 static GPtrArray     *items2del=NULL;
00060 static LettersItem   *item_on_focus=NULL;
00061 
00062 
00063 static GcomprisBoard *gcomprisBoard = NULL;
00064 
00065 static gint dummy_id = 0;
00066 static gint drop_items_id = 0;
00067 
00068 
00069 static void           start_board (GcomprisBoard *agcomprisBoard);
00070 static void           pause_board (gboolean pause);
00071 static void           end_board (void);
00072 static gboolean              is_our_board (GcomprisBoard *gcomprisBoard);
00073 static void           set_level (guint level);
00074 static gint           key_press(guint keyval, gchar *commit_str, gchar *preedit_str);
00075 
00076 static GnomeCanvasItem       *wordsgame_create_item(GnomeCanvasGroup *parent);
00077 static gint           wordsgame_drop_items (GtkWidget *widget, gpointer data);
00078 static gint           wordsgame_move_items (GtkWidget *widget, gpointer data);
00079 static void           wordsgame_destroy_item(LettersItem *item);
00080 static gboolean              wordsgame_destroy_items(GPtrArray *items);
00081 static void           wordsgame_destroy_all_items(void);
00082 static void           wordsgame_next_level(void);
00083 static void           wordsgame_add_new_item(void);
00084 
00085 static void           player_win(LettersItem *item);
00086 static void           player_loose(void);
00087 
00088 
00089 #define MAX_FALLSPEED  7000
00090 #define MAX_SPEED  150
00091 #define MIN_FALLSPEED  3000
00092 #define MIN_SPEED  50
00093 #define DEFAULT_FALLSPEED  7000
00094 #define DEFAULT_SPEED  150
00095 
00096 #define INCREMENT_FALLSPEED  1000
00097 #define INCREMENT_SPEED  10
00098 
00099 
00100 static  guint32              fallSpeed = 0;
00101 static  double               speed = 0.0;
00102 
00103 static GnomeCanvasItem *preedit_text = NULL;
00104 
00105 /* Description of this plugin */
00106 static BoardPlugin menu_bp =
00107   {
00108     NULL,
00109     NULL,
00110     N_("Falling Words"),
00111     N_("Type the falling words before they reach the ground"),
00112     "Bruno Coudoin <bruno.coudoin@free.fr>",
00113     NULL,
00114     NULL,
00115     NULL,
00116     NULL,
00117     start_board,
00118     pause_board,
00119     end_board,
00120     is_our_board,
00121     key_press,
00122     NULL,
00123     set_level,
00124     NULL,
00125     NULL,
00126     NULL,
00127     NULL
00128   };
00129 
00130 /*
00131  * Main entry point mandatory for each Gcompris's game
00132  * ---------------------------------------------------
00133  *
00134  */
00135 
00136 GET_BPLUGIN_INFO(wordsgame)
00137 
00138 /*
00139  * in : boolean TRUE = PAUSE : FALSE = UNPAUSE
00140  *
00141  */
00142      static void pause_board (gboolean pause)
00143 {
00144 
00145   if(gcomprisBoard==NULL)
00146     return;
00147 
00148   if(pause)
00149     {
00150       if (dummy_id) {
00151        g_source_remove (dummy_id);
00152        dummy_id = 0;
00153       }
00154       if (drop_items_id) {
00155        g_source_remove (drop_items_id);
00156        drop_items_id = 0;
00157       }
00158     }
00159   else
00160     {
00161       if(!drop_items_id) {
00162        drop_items_id = g_timeout_add (0,
00163                                    (GtkFunction) wordsgame_drop_items, NULL);
00164       }
00165       if(!dummy_id) {
00166        dummy_id = g_timeout_add (10, (GtkFunction) wordsgame_move_items, NULL);
00167       }
00168     }
00169 }
00170 
00171 /*
00172  */
00173 static void start_board (GcomprisBoard *agcomprisBoard)
00174 {
00175 
00176   if(agcomprisBoard!=NULL)
00177     {
00178       gcomprisBoard=agcomprisBoard;
00179 
00180       /* disable im_context */
00181       //gcomprisBoard->disable_im_context = TRUE;
00182 
00183       gc_set_background(gnome_canvas_root(gcomprisBoard->canvas), "images/scenery_background.png");
00184 
00185 
00186       gcomprisBoard->level = 1;
00187       gcomprisBoard->maxlevel = 6;
00188       gcomprisBoard->sublevel = 0;
00189       gc_bar_set(GC_BAR_LEVEL);
00190 
00191       /* Default speed */
00192       speed=DEFAULT_SPEED;
00193       fallSpeed=DEFAULT_FALLSPEED;
00194 
00195       gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-$LOCALE.xml");
00196 
00197       if(!gc_wordlist)
00198        {
00199          /* Fallback to english */
00200          gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-en.xml");
00201 
00202          if(!gc_wordlist)
00203            {
00204              gcomprisBoard = NULL;
00205              gc_dialog(_("Error: We can't find\na list of words to play this game.\n"), gc_board_end);
00206              return;
00207            }
00208        }
00209 
00210       wordsgame_next_level();
00211     }
00212 }
00213 
00214 static void
00215 end_board ()
00216 {
00217 
00218   if(gcomprisBoard!=NULL)
00219     {
00220       pause_board(TRUE);
00221       gc_score_end();
00222       wordsgame_destroy_all_items();
00223       if (preedit_text){
00224        gtk_object_destroy(GTK_OBJECT(preedit_text));
00225        preedit_text=NULL;
00226       }
00227       gc_im_reset();
00228       gcomprisBoard = NULL;
00229 
00230       if (gc_wordlist != NULL){
00231        gc_wordlist_free(gc_wordlist);
00232        gc_wordlist = NULL;
00233       }
00234 
00235     }
00236 }
00237 
00238 static void
00239 set_level (guint level)
00240 {
00241 
00242   if(gcomprisBoard!=NULL)
00243     {
00244       gcomprisBoard->level=level;
00245       wordsgame_next_level();
00246     }
00247 }
00248 
00249 
00250 static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str)
00251 {
00252   gchar *letter;
00253   gint i;
00254   LettersItem *item;
00255   gchar *str;
00256   gunichar unichar_letter;
00257 
00258   if(!gcomprisBoard)
00259     return FALSE;
00260 
00261   if (keyval){
00262     g_warning("keyval %d", keyval);
00263     return TRUE;
00264   }
00265 
00266   if (preedit_str){
00267     g_warning("preedit_str %s", preedit_str);
00268     /* show the preedit string on bottom of the window */
00269     GcomprisProperties      *properties = gc_prop_get ();
00270     gchar *text;
00271     PangoAttrList *attrs;
00272     gint cursor_pos;
00273     gtk_im_context_get_preedit_string (properties->context,
00274                                    &text,
00275                                    &attrs,
00276                                    &cursor_pos);
00277 
00278     if (!preedit_text)
00279       preedit_text = \
00280        gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
00281                             gnome_canvas_text_get_type (),
00282                             "font", gc_skin_font_board_huge_bold,
00283                             "x", (double) BOARDWIDTH/2,
00284                             "y", (double) BOARDHEIGHT - 100,
00285                             "anchor", GTK_ANCHOR_N,
00286                             //"fill_color_rgba", 0xba00ffff,
00287                             NULL);
00288 
00289 
00290     gnome_canvas_item_set (preedit_text,
00291                         "text", text,
00292                         "attributes", attrs,
00293                         NULL);
00294 
00295     return TRUE;
00296 
00297   }
00298 
00299   /* commit str */
00300   g_warning("commit_str %s", commit_str);
00301 
00302   str = commit_str;
00303 
00304   for (i=0; i < g_utf8_strlen(commit_str,-1); i++){
00305     unichar_letter = g_utf8_get_char(str);
00306     str = g_utf8_next_char(str);
00307     if(!g_unichar_isalnum (unichar_letter)){
00308       player_loose();
00309       return FALSE;
00310     }
00311 
00312     letter = g_new0(gchar,6);
00313     g_unichar_to_utf8 (unichar_letter, letter);
00314 
00315     if(item_on_focus==NULL)
00316       {
00317        g_static_rw_lock_reader_lock (&items_lock);
00318        gint count=items->len;
00319        g_static_rw_lock_reader_unlock (&items_lock);
00320 
00321        for (i=0;i<count;i++)
00322          {
00323            g_static_rw_lock_reader_lock (&items_lock);
00324            item=g_ptr_array_index(items,i);
00325            g_static_rw_lock_reader_unlock (&items_lock);
00326            g_assert (item!=NULL);
00327            if (strcmp(item->letter,letter)==0)
00328              {
00329               item_on_focus=item;
00330               break;
00331              }
00332          }
00333       }
00334 
00335 
00336     if(item_on_focus!=NULL)
00337       {
00338 
00339        if(strcmp(item_on_focus->letter, letter)==0)
00340          {
00341            item_on_focus->count++;
00342            g_free(item_on_focus->overword);
00343            item_on_focus->overword=g_utf8_strndup(item_on_focus->word,item_on_focus->count);
00344            gnome_canvas_item_set (item_on_focus->overwriteItem,
00345                                "text", item_on_focus->overword,
00346                                NULL);
00347 
00348 
00349            if (item_on_focus->count<g_utf8_strlen(item_on_focus->word,-1))
00350              {
00351               g_free(item_on_focus->letter);
00352               item_on_focus->letter=g_utf8_strndup(item_on_focus->pos,1);
00353               item_on_focus->pos=g_utf8_find_next_char(item_on_focus->pos,NULL);
00354              }
00355            else
00356              {
00357               player_win(item_on_focus);
00358               item_on_focus=NULL;
00359              }
00360          }
00361        else
00362          {
00363            /* It is a loose : unselect the word and defocus */
00364            g_free(item_on_focus->overword);
00365            item_on_focus->overword=g_strdup(" ");
00366            item_on_focus->count=0;
00367            g_free(item_on_focus->letter);
00368            item_on_focus->letter=g_utf8_strndup(item_on_focus->word,1);
00369 
00370            item_on_focus->pos=g_utf8_find_next_char(item_on_focus->word,NULL);
00371 
00372            gnome_canvas_item_set (item_on_focus->overwriteItem,
00373                                "text", item_on_focus->overword,
00374                                NULL);
00375            item_on_focus=NULL;
00376            g_free(letter);
00377            player_loose();
00378            break;
00379          }
00380       }
00381     else
00382       {
00383        /* Anyway kid you clicked on the wrong key */
00384        player_loose();
00385        g_free(letter);
00386        break;
00387       }
00388 
00389     g_free(letter);
00390   }
00391   return TRUE;
00392 }
00393 
00394 static gboolean
00395 is_our_board (GcomprisBoard *gcomprisBoard)
00396 {
00397   if (gcomprisBoard)
00398     {
00399       if(g_strcasecmp(gcomprisBoard->type, "wordsgame")==0)
00400        {
00401          /* Set the plugin entry */
00402          gcomprisBoard->plugin=&menu_bp;
00403 
00404          return TRUE;
00405        }
00406     }
00407   return FALSE;
00408 }
00409 
00410 
00411 /*-------------------------------------------------------------------------------*/
00412 /*-------------------------------------------------------------------------------*/
00413 /*-------------------------------------------------------------------------------*/
00414 /*-------------------------------------------------------------------------------*/
00415 
00416 /* set initial values for the next level */
00417 static void wordsgame_next_level()
00418 {
00419 
00420 
00421   gcomprisBoard->number_of_sublevel = 10 +
00422     ((gcomprisBoard->level-1) * 5);
00423   gc_score_start(SCORESTYLE_NOTE,
00424                      gcomprisBoard->width - 220,
00425                      gcomprisBoard->height - 50,
00426                      gcomprisBoard->number_of_sublevel);
00427 
00428   gc_bar_set_level(gcomprisBoard);
00429   gc_score_set(gcomprisBoard->sublevel);
00430 
00431   wordsgame_destroy_all_items();
00432 
00433   if (preedit_text){
00434     gtk_object_destroy(GTK_OBJECT(preedit_text));
00435     preedit_text=NULL;
00436   }
00437   gc_im_reset();
00438 
00439   items=g_ptr_array_new();
00440   items2del=g_ptr_array_new();
00441 
00442 
00443   /* Increase speed only after 5 levels */
00444   if(gcomprisBoard->level>5)
00445     {
00446       gint temp = fallSpeed-gcomprisBoard->level*200;
00447       if (temp > MIN_FALLSPEED)    fallSpeed=temp;
00448     }
00449 
00450   pause_board(FALSE);
00451 }
00452 
00453 
00454 static void wordsgame_move_item(LettersItem *item)
00455 {
00456   double x1, y1, x2, y2;
00457 
00458 
00459   gnome_canvas_item_move(item->rootitem, 0, 2.0);
00460 
00461   gnome_canvas_item_get_bounds    (item->rootitem,
00462                                &x1,
00463                                &y1,
00464                                &x2,
00465                                &y2);
00466 
00467   if(y1>gcomprisBoard->height) {
00468 
00469     if (item == item_on_focus)
00470       item_on_focus = NULL;
00471 
00472     g_static_rw_lock_writer_lock (&items_lock);
00473     g_ptr_array_remove (items, item);
00474     g_static_rw_lock_writer_unlock (&items_lock);
00475 
00476     g_static_rw_lock_writer_lock (&items2del_lock);
00477     g_ptr_array_add (items2del, item);
00478     g_static_rw_lock_writer_unlock (&items2del_lock);
00479 
00480     g_timeout_add (100,(GtkFunction) wordsgame_destroy_items, items2del);
00481 
00482     player_loose();
00483   }
00484 }
00485 
00486 /*
00487  * This does the moves of the game items on the play canvas
00488  *
00489  */
00490 static gint wordsgame_move_items (GtkWidget *widget, gpointer data)
00491 {
00492   g_assert (items!=NULL);
00493   gint i;
00494   LettersItem *item;
00495 
00496   for (i=items->len-1;i>=0;i--)
00497     {
00498 
00499       g_static_rw_lock_reader_lock (&items_lock);
00500       item=g_ptr_array_index(items,i);
00501       g_static_rw_lock_reader_unlock (&items_lock);
00502       wordsgame_move_item(item);
00503     }
00504   dummy_id = g_timeout_add (speed,(GtkFunction) wordsgame_move_items, NULL);
00505   return (FALSE);
00506 }
00507 
00508 
00509 
00510 static void wordsgame_destroy_item(LettersItem *item)
00511 {
00512 
00513   /* The items are freed by player_win */
00514   gtk_object_destroy (GTK_OBJECT(item->rootitem));
00515   g_free(item->word);
00516   g_free(item->overword);
00517   g_free(item->letter);
00518   g_free(item);
00519 }
00520 
00521 /* Destroy items that falls out of the canvas */
00522 static gboolean wordsgame_destroy_items(GPtrArray *item_list)
00523 {
00524   LettersItem *item;
00525 
00526   g_assert(item_list!=NULL);
00527 
00528 
00529 
00530   if  (item_list==items) {
00531     g_static_rw_lock_writer_lock (&items_lock);
00532     while (item_list->len>0)
00533       {
00534        item = g_ptr_array_index(item_list,0);
00535        g_ptr_array_remove_index_fast(item_list,0);
00536        wordsgame_destroy_item(item);
00537       }
00538     g_static_rw_lock_writer_unlock (&items_lock);
00539   }
00540 
00541   if  (item_list==items2del) {
00542     g_static_rw_lock_writer_lock (&items2del_lock);
00543     while (item_list->len>0)
00544       {
00545        item = g_ptr_array_index(item_list,0);
00546        g_ptr_array_remove_index_fast(item_list,0);
00547        wordsgame_destroy_item(item);
00548       }
00549     g_static_rw_lock_writer_unlock (&items2del_lock);
00550   }
00551 
00552   return (FALSE);
00553 }
00554 
00555 /* Destroy all the items */
00556 static void wordsgame_destroy_all_items()
00557 {
00558 
00559   if (items!=NULL){
00560     if(items->len > 0) {
00561       wordsgame_destroy_items(items);
00562     }
00563     g_ptr_array_free (items, TRUE);
00564     items=NULL;
00565   }
00566 
00567   if (items2del!=NULL){
00568     if(items2del->len > 0) {
00569       wordsgame_destroy_items(items2del);
00570     }
00571     g_ptr_array_free (items2del, TRUE);
00572     items2del=NULL;
00573   }
00574 
00575 }
00576 
00577 
00578 static GnomeCanvasItem *wordsgame_create_item(GnomeCanvasGroup *parent)
00579 {
00580 
00581   GnomeCanvasItem *item2;
00582   LettersItem *item;
00583   gchar *word = gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level);
00584 
00585   if(!word)
00586     /* Should display the dialog box here */
00587     return NULL;
00588 
00589   // create and init item
00590   item = g_new(LettersItem,1);
00591   item->word = word;
00592   item->overword=g_strdup("");
00593   item->count=0;
00594   item->letter=g_utf8_strndup(item->word,1);
00595   item->pos=g_utf8_find_next_char(item->word,NULL);
00596 
00597   item->rootitem = \
00598     gnome_canvas_item_new (parent,
00599                         gnome_canvas_group_get_type (),
00600                         "x", (double) 0,
00601                         "y", (double) -12,
00602                         NULL);
00603 
00604   /* To 'erase' words, I create 2 times the text item. One is empty now */
00605   /* It will be filled each time the user enters the right key         */
00606   item2 = \
00607     gnome_canvas_item_new (GNOME_CANVAS_GROUP(item->rootitem),
00608                         gnome_canvas_text_get_type (),
00609                         "text", item->word,
00610                         "font", gc_skin_font_board_huge_bold,
00611                         "x", (double) 0,
00612                         "y", (double) 0,
00613                         "anchor", GTK_ANCHOR_NW,
00614                         "fill_color_rgba", 0xba00ffff,
00615                         NULL);
00616 
00617   item->overwriteItem = \
00618     gnome_canvas_item_new (GNOME_CANVAS_GROUP(item->rootitem),
00619                         gnome_canvas_text_get_type (),
00620                         "text", item->overword,
00621                         "font", gc_skin_font_board_huge_bold,
00622                         "x", (double) 0,
00623                         "y", (double) 0,
00624                         "anchor", GTK_ANCHOR_NW,
00625                         "fill_color", "blue",
00626                         NULL);
00627 
00628   /*set right x position */
00629 
00630   double x1, y1, x2, y2;
00631 
00632 
00633   gnome_canvas_item_get_bounds    (item->rootitem,
00634                                    &x1,
00635                                    &y1,
00636                                    &x2,
00637                                    &y2);
00638 
00639   gnome_canvas_item_move (item->rootitem,(double) (rand()%(gcomprisBoard->width-(gint)(x2))),(double) 0);
00640 
00641 
00642   g_static_rw_lock_writer_lock (&items_lock);
00643   g_ptr_array_add(items, item);
00644   g_static_rw_lock_writer_unlock (&items_lock);
00645 
00646   return (item->rootitem);
00647 }
00648 
00649 static void wordsgame_add_new_item()
00650 {
00651 
00652   g_assert(gcomprisBoard->canvas!=NULL);
00653   wordsgame_create_item(gnome_canvas_root(gcomprisBoard->canvas));
00654 
00655 }
00656 
00657 /*
00658  * This is called on a low frequency and is used to drop new items
00659  *
00660  */
00661 static gint wordsgame_drop_items (GtkWidget *widget, gpointer data)
00662 {
00663   wordsgame_add_new_item();
00664   g_source_remove(drop_items_id);
00665   drop_items_id = g_timeout_add (fallSpeed,(GtkFunction) wordsgame_drop_items, NULL);
00666 
00667   return (FALSE);
00668 }
00669 
00670 static void player_win(LettersItem *item)
00671 {
00672 
00673   gc_sound_play_ogg ("sounds/gobble.ogg", NULL);
00674 
00675   g_assert(gcomprisBoard!=NULL);
00676 
00677   gcomprisBoard->sublevel++;
00678   gc_score_set(gcomprisBoard->sublevel);
00679 
00680 
00681   g_static_rw_lock_writer_lock (&items_lock);
00682   g_ptr_array_remove(items,item);
00683   g_static_rw_lock_writer_unlock (&items_lock);
00684 
00685   g_static_rw_lock_writer_lock (&items2del_lock);
00686   g_ptr_array_add(items2del,item);
00687   g_static_rw_lock_writer_unlock (&items2del_lock);
00688 
00689   gnome_canvas_item_hide(item->rootitem);
00690   g_timeout_add (500,(GtkFunction) wordsgame_destroy_items, items2del);
00691 
00692 
00693   if(gcomprisBoard->sublevel > gcomprisBoard->number_of_sublevel)
00694     {
00695 
00696       /* Try the next level */
00697       gcomprisBoard->level++;
00698       gcomprisBoard->sublevel = 0;
00699       if(gcomprisBoard->level>gcomprisBoard->maxlevel) { // the current board is finished : bail out
00700        gc_bonus_end_display(BOARD_FINISHED_RANDOM);
00701        return;
00702       }
00703       wordsgame_next_level();
00704       gc_sound_play_ogg ("sounds/bonus.ogg", NULL);
00705     }
00706   else
00707     {
00708 
00709       /* Drop a new item now to speed up the game */
00710       g_static_rw_lock_reader_lock (&items_lock);
00711       gint count=items->len;
00712       g_static_rw_lock_reader_unlock (&items_lock);
00713 
00714       if(count==0)
00715         {
00716 
00717          if ((fallSpeed-=INCREMENT_FALLSPEED) < MIN_FALLSPEED) fallSpeed+=INCREMENT_FALLSPEED;
00718 
00719          if ((speed-=INCREMENT_SPEED) < MIN_SPEED) speed+=INCREMENT_SPEED;
00720 
00721           if (drop_items_id) {
00722             /* Remove pending new item creation to sync the falls */
00723             g_source_remove (drop_items_id);
00724             drop_items_id = 0;
00725           }
00726 
00727           if(!drop_items_id) {
00728             drop_items_id = g_timeout_add (0,
00729                                       (GtkFunction) wordsgame_drop_items,
00730                                       NULL);
00731           }
00732 
00733         }
00734     }
00735 
00736 }
00737 
00738 static void player_loose()
00739 {
00740   gc_sound_play_ogg ("sounds/crash.ogg", NULL);
00741 }