/* gcompris - wordsgame.c
*
* Copyright (C) 2000 Bruno Coudoin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include "gcompris/gcompris.h"
#include "gcompris/gameutil.h"
#define SOUNDLISTFILE PACKAGE
#define MAXWORDSLENGTH 50
static GcomprisWordlist *gc_wordlist = NULL;
static gboolean board_paused = TRUE;
GStaticRWLock items_lock = G_STATIC_RW_LOCK_INIT;
GStaticRWLock items2del_lock = G_STATIC_RW_LOCK_INIT;
/*
word - word to type
overword - part of word allready typed
count - number of allready typed letters in word
pos - pointer to current position in word
letter - current expected letter to type
*/
typedef struct {
GnomeCanvasItem *rootitem;
GnomeCanvasItem *overwriteItem;
gchar *word;
gchar *overword;
gint count;
gchar *pos;
gchar *letter;
} LettersItem;
/*
items - array of displayed items
items2del - array of items where moved offscreen
item_on_focus - item on focus in array items. NULL - not set.
*/
static GPtrArray *items=NULL;
static GPtrArray *items2del=NULL;
static LettersItem *item_on_focus=NULL;
static GcomprisBoard *gcomprisBoard = NULL;
static gint dummy_id = 0;
static gint drop_items_id = 0;
static void start_board (GcomprisBoard *agcomprisBoard);
static void pause_board (gboolean pause);
static void end_board (void);
static gboolean is_our_board (GcomprisBoard *gcomprisBoard);
static void set_level (guint level);
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str);
static GnomeCanvasItem *wordsgame_create_item(GnomeCanvasGroup *parent);
static gint wordsgame_drop_items (GtkWidget *widget, gpointer data);
static gint wordsgame_move_items (GtkWidget *widget, gpointer data);
static void wordsgame_destroy_item(LettersItem *item);
static gboolean wordsgame_destroy_items(GPtrArray *items);
static void wordsgame_destroy_all_items(void);
static void wordsgame_next_level(void);
static void wordsgame_add_new_item(void);
static void wordsgame_config_start(GcomprisBoard *agcomprisBoard,
GcomprisProfile *aProfile);
static void wordsgame_config_stop(void);
static void player_win(LettersItem *item);
static void player_loose(void);
#define MAX_FALLSPEED 7000
#define MAX_SPEED 150
#define MIN_FALLSPEED 3000
#define MIN_SPEED 50
#define DEFAULT_FALLSPEED 7000
#define DEFAULT_SPEED 150
#define INCREMENT_FALLSPEED 1000
#define INCREMENT_SPEED 10
static guint32 fallSpeed = 0;
static double speed = 0.0;
static GnomeCanvasItem *preedit_text = NULL;
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
N_("Falling Words"),
N_("Type the falling words before they reach the ground"),
"Bruno Coudoin ",
NULL,
NULL,
NULL,
NULL,
start_board,
pause_board,
end_board,
is_our_board,
key_press,
NULL,
set_level,
NULL,
NULL,
wordsgame_config_start,
wordsgame_config_stop
};
/*
* Main entry point mandatory for each Gcompris's game
* ---------------------------------------------------
*
*/
GET_BPLUGIN_INFO(wordsgame)
/*
* in : boolean TRUE = PAUSE : FALSE = UNPAUSE
*
*/
static void pause_board (gboolean pause)
{
if(gcomprisBoard==NULL)
return;
if(pause)
{
if (dummy_id) {
g_source_remove (dummy_id);
dummy_id = 0;
}
if (drop_items_id) {
g_source_remove (drop_items_id);
drop_items_id = 0;
}
}
else
{
if(!drop_items_id) {
drop_items_id = g_timeout_add (0,
(GtkFunction) wordsgame_drop_items, NULL);
}
if(!dummy_id) {
dummy_id = g_timeout_add (10, (GtkFunction) wordsgame_move_items, NULL);
}
}
board_paused = pause;
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
/* disable im_context */
//gcomprisBoard->disable_im_context = TRUE;
gc_set_background(gnome_canvas_root(gcomprisBoard->canvas),
"opt/scenery_background.png");
gcomprisBoard->level = 1;
gcomprisBoard->maxlevel = 6;
gcomprisBoard->sublevel = 0;
gc_bar_set(GC_BAR_LEVEL|GC_BAR_CONFIG);
/* Default speed */
speed=DEFAULT_SPEED;
fallSpeed=DEFAULT_FALLSPEED;
gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-$LOCALE.xml");
if(!gc_wordlist)
{
/* Fallback to english */
gc_wordlist = gc_wordlist_get_from_file("wordsgame/default-en.xml");
if(!gc_wordlist)
{
gcomprisBoard = NULL;
gc_dialog(_("Error: We can't find\na list of words to play this game.\n"), gc_board_end);
return;
}
}
wordsgame_next_level();
}
}
static void
end_board ()
{
if(gcomprisBoard!=NULL)
{
pause_board(TRUE);
gc_score_end();
wordsgame_destroy_all_items();
if (preedit_text){
gtk_object_destroy(GTK_OBJECT(preedit_text));
preedit_text=NULL;
}
gc_im_reset();
gcomprisBoard = NULL;
if (gc_wordlist != NULL){
gc_wordlist_free(gc_wordlist);
gc_wordlist = NULL;
}
}
}
static void
set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
wordsgame_next_level();
}
}
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str)
{
gchar *letter;
gint i;
LettersItem *item;
gchar *str;
gunichar unichar_letter;
if(board_paused && !gcomprisBoard)
return FALSE;
if (keyval){
g_warning("keyval %d", keyval);
return TRUE;
}
if (preedit_str){
g_warning("preedit_str %s", preedit_str);
/* show the preedit string on bottom of the window */
GcomprisProperties *properties = gc_prop_get ();
gchar *text;
PangoAttrList *attrs;
gint cursor_pos;
gtk_im_context_get_preedit_string (properties->context,
&text,
&attrs,
&cursor_pos);
if (!preedit_text)
preedit_text = \
gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
gnome_canvas_text_get_type (),
"font", gc_skin_font_board_huge_bold,
"x", (double) BOARDWIDTH/2,
"y", (double) BOARDHEIGHT - 100,
"anchor", GTK_ANCHOR_N,
//"fill_color_rgba", 0xba00ffff,
NULL);
gnome_canvas_item_set (preedit_text,
"text", text,
"attributes", attrs,
NULL);
return TRUE;
}
/* commit str */
g_warning("commit_str %s", commit_str);
str = commit_str;
for (i=0; i < g_utf8_strlen(commit_str,-1); i++){
unichar_letter = g_utf8_get_char(str);
str = g_utf8_next_char(str);
if(!g_unichar_isalnum (unichar_letter)){
player_loose();
return FALSE;
}
letter = g_new0(gchar,6);
g_unichar_to_utf8 (unichar_letter, letter);
if(item_on_focus==NULL)
{
g_static_rw_lock_reader_lock (&items_lock);
gint count=items->len;
g_static_rw_lock_reader_unlock (&items_lock);
for (i=0;iletter,letter)==0)
{
item_on_focus=item;
break;
}
}
}
if(item_on_focus!=NULL)
{
if(strcmp(item_on_focus->letter, letter)==0)
{
gchar *tmpstr;
item_on_focus->count++;
g_free(item_on_focus->overword);
tmpstr = g_utf8_strndup(item_on_focus->word,
item_on_focus->count);
/* Add the ZERO WIDTH JOINER to force joined char in Arabic and Hangul
* http://en.wikipedia.org/wiki/Zero-width_joiner (UTF-8: e2808d)
*/
item_on_focus->overword = g_strdup_printf("%s%c%c%c", tmpstr, 0xe2, 0x80, 0x8d);
g_free(tmpstr);
gnome_canvas_item_set (item_on_focus->overwriteItem,
"text", item_on_focus->overword,
NULL);
if (item_on_focus->countword,-1))
{
g_free(item_on_focus->letter);
item_on_focus->letter=g_utf8_strndup(item_on_focus->pos,1);
item_on_focus->pos=g_utf8_find_next_char(item_on_focus->pos,NULL);
}
else
{
player_win(item_on_focus);
item_on_focus=NULL;
}
}
else
{
/* It is a loose : unselect the word and defocus */
g_free(item_on_focus->overword);
item_on_focus->overword=g_strdup(" ");
item_on_focus->count=0;
g_free(item_on_focus->letter);
item_on_focus->letter=g_utf8_strndup(item_on_focus->word,1);
item_on_focus->pos=g_utf8_find_next_char(item_on_focus->word,NULL);
gnome_canvas_item_set (item_on_focus->overwriteItem,
"text", item_on_focus->overword,
NULL);
item_on_focus=NULL;
g_free(letter);
player_loose();
break;
}
}
else
{
/* Anyway kid you clicked on the wrong key */
player_loose();
g_free(letter);
break;
}
g_free(letter);
}
return TRUE;
}
static gboolean
is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_strcasecmp(gcomprisBoard->type, "wordsgame")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void wordsgame_next_level()
{
gcomprisBoard->number_of_sublevel = 10 +
((gcomprisBoard->level-1) * 5);
gc_score_start(SCORESTYLE_NOTE,
gcomprisBoard->width - 220,
gcomprisBoard->height - 50,
gcomprisBoard->number_of_sublevel);
gc_bar_set_level(gcomprisBoard);
gc_score_set(gcomprisBoard->sublevel);
wordsgame_destroy_all_items();
if (preedit_text){
gtk_object_destroy(GTK_OBJECT(preedit_text));
preedit_text=NULL;
}
gc_im_reset();
items=g_ptr_array_new();
items2del=g_ptr_array_new();
/* Increase speed only after 5 levels */
if(gcomprisBoard->level>5)
{
gint temp = fallSpeed-gcomprisBoard->level*200;
if (temp > MIN_FALLSPEED) fallSpeed=temp;
}
pause_board(FALSE);
}
static void wordsgame_move_item(LettersItem *item)
{
double x1, y1, x2, y2;
gnome_canvas_item_move(item->rootitem, 0, 2.0);
gnome_canvas_item_get_bounds (item->rootitem,
&x1,
&y1,
&x2,
&y2);
if(y1>gcomprisBoard->height) {
if (item == item_on_focus)
item_on_focus = NULL;
g_static_rw_lock_writer_lock (&items_lock);
g_ptr_array_remove (items, item);
g_static_rw_lock_writer_unlock (&items_lock);
g_static_rw_lock_writer_lock (&items2del_lock);
g_ptr_array_add (items2del, item);
g_static_rw_lock_writer_unlock (&items2del_lock);
g_timeout_add (100,(GtkFunction) wordsgame_destroy_items, items2del);
player_loose();
}
}
/*
* This does the moves of the game items on the play canvas
*
*/
static gint wordsgame_move_items (GtkWidget *widget, gpointer data)
{
g_assert (items!=NULL);
gint i;
LettersItem *item;
for (i=items->len-1;i>=0;i--)
{
g_static_rw_lock_reader_lock (&items_lock);
item=g_ptr_array_index(items,i);
g_static_rw_lock_reader_unlock (&items_lock);
wordsgame_move_item(item);
}
dummy_id = g_timeout_add (speed,(GtkFunction) wordsgame_move_items, NULL);
return (FALSE);
}
static void wordsgame_destroy_item(LettersItem *item)
{
/* The items are freed by player_win */
gtk_object_destroy (GTK_OBJECT(item->rootitem));
g_free(item->word);
g_free(item->overword);
g_free(item->letter);
g_free(item);
}
/* Destroy items that falls out of the canvas */
static gboolean wordsgame_destroy_items(GPtrArray *item_list)
{
LettersItem *item;
g_assert(item_list!=NULL);
if (item_list==items) {
g_static_rw_lock_writer_lock (&items_lock);
while (item_list->len>0)
{
item = g_ptr_array_index(item_list,0);
g_ptr_array_remove_index_fast(item_list,0);
wordsgame_destroy_item(item);
}
g_static_rw_lock_writer_unlock (&items_lock);
}
if (item_list==items2del) {
g_static_rw_lock_writer_lock (&items2del_lock);
while (item_list->len>0)
{
item = g_ptr_array_index(item_list,0);
g_ptr_array_remove_index_fast(item_list,0);
wordsgame_destroy_item(item);
}
g_static_rw_lock_writer_unlock (&items2del_lock);
}
return (FALSE);
}
/* Destroy all the items */
static void wordsgame_destroy_all_items()
{
if (items!=NULL){
if(items->len > 0) {
wordsgame_destroy_items(items);
}
g_ptr_array_free (items, TRUE);
items=NULL;
}
if (items2del!=NULL){
if(items2del->len > 0) {
wordsgame_destroy_items(items2del);
}
g_ptr_array_free (items2del, TRUE);
items2del=NULL;
}
}
static GnomeCanvasItem *wordsgame_create_item(GnomeCanvasGroup *parent)
{
GnomeCanvasItem *item2;
LettersItem *item;
gchar *word = gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level);
GtkAnchorType direction_anchor = GTK_ANCHOR_NW;
if(!word)
/* Should display the dialog box here */
return NULL;
// create and init item
item = g_new(LettersItem,1);
item->word = word;
item->overword=g_strdup("");
item->count=0;
item->letter=g_utf8_strndup(item->word,1);
item->pos=g_utf8_find_next_char(item->word, NULL);
if (pango_unichar_direction(g_utf8_get_char(item->word)))
direction_anchor = GTK_ANCHOR_NE;
item->rootitem = \
gnome_canvas_item_new (parent,
gnome_canvas_group_get_type (),
"x", (double) 0,
"y", (double) -12,
NULL);
/* To 'erase' words, I create 2 times the text item. One is empty now */
/* It will be filled each time the user enters the right key */
item2 = \
gnome_canvas_item_new (GNOME_CANVAS_GROUP(item->rootitem),
gnome_canvas_text_get_type (),
"text", item->word,
"font", gc_skin_font_board_huge_bold,
"x", (double) 0,
"y", (double) 0,
"anchor", direction_anchor,
"fill_color_rgba", 0xba00ffff,
NULL);
item->overwriteItem = \
gnome_canvas_item_new (GNOME_CANVAS_GROUP(item->rootitem),
gnome_canvas_text_get_type (),
"text", item->overword,
"font", gc_skin_font_board_huge_bold,
"x", (double) 0,
"y", (double) 0,
"anchor", direction_anchor,
"fill_color", "blue",
NULL);
/*set right x position */
double x1, y1, x2, y2;
gnome_canvas_item_get_bounds (item->rootitem,
&x1,
&y1,
&x2,
&y2);
if(direction_anchor == GTK_ANCHOR_NW)
gnome_canvas_item_move (item->rootitem,(double) (g_random_int()%(gcomprisBoard->width-(gint)(x2))),(double) 0);
else
{
double new_x = (double)( g_random_int()%gcomprisBoard->width);
if ( new_x < -x1 )
new_x -= x1;
gnome_canvas_item_move (item->rootitem, new_x ,(double) 0);
}
g_static_rw_lock_writer_lock (&items_lock);
g_ptr_array_add(items, item);
g_static_rw_lock_writer_unlock (&items_lock);
return (item->rootitem);
}
static void wordsgame_add_new_item()
{
g_assert(gcomprisBoard->canvas!=NULL);
wordsgame_create_item(gnome_canvas_root(gcomprisBoard->canvas));
}
/*
* This is called on a low frequency and is used to drop new items
*
*/
static gint wordsgame_drop_items (GtkWidget *widget, gpointer data)
{
gc_sound_play_ogg ("sounds/level.wav", NULL);
wordsgame_add_new_item();
g_source_remove(drop_items_id);
drop_items_id = g_timeout_add (fallSpeed,(GtkFunction) wordsgame_drop_items, NULL);
return (FALSE);
}
static void player_win(LettersItem *item)
{
gc_sound_play_ogg ("sounds/flip.wav", NULL);
g_assert(gcomprisBoard!=NULL);
gcomprisBoard->sublevel++;
gc_score_set(gcomprisBoard->sublevel);
g_static_rw_lock_writer_lock (&items_lock);
g_ptr_array_remove(items,item);
g_static_rw_lock_writer_unlock (&items_lock);
g_static_rw_lock_writer_lock (&items2del_lock);
g_ptr_array_add(items2del,item);
g_static_rw_lock_writer_unlock (&items2del_lock);
gnome_canvas_item_hide(item->rootitem);
g_timeout_add (500,(GtkFunction) wordsgame_destroy_items, items2del);
if(gcomprisBoard->sublevel > gcomprisBoard->number_of_sublevel)
{
/* Try the next level */
gcomprisBoard->level++;
gcomprisBoard->sublevel = 0;
if(gcomprisBoard->level>gcomprisBoard->maxlevel) { // the current board is finished : bail out
gc_bonus_end_display(GC_BOARD_FINISHED_RANDOM);
return;
}
wordsgame_next_level();
gc_sound_play_ogg ("sounds/bonus.wav", NULL);
}
else
{
/* Drop a new item now to speed up the game */
g_static_rw_lock_reader_lock (&items_lock);
gint count=items->len;
g_static_rw_lock_reader_unlock (&items_lock);
if(count==0)
{
if ((fallSpeed-=INCREMENT_FALLSPEED) < MIN_FALLSPEED) fallSpeed+=INCREMENT_FALLSPEED;
if ((speed-=INCREMENT_SPEED) < MIN_SPEED) speed+=INCREMENT_SPEED;
if (drop_items_id) {
/* Remove pending new item creation to sync the falls */
g_source_remove (drop_items_id);
drop_items_id = 0;
}
if(!drop_items_id) {
drop_items_id = g_timeout_add (0,
(GtkFunction) wordsgame_drop_items,
NULL);
}
}
}
}
static void player_loose()
{
gc_sound_play_ogg ("sounds/crash.wav", NULL);
}
static void conf_ok(gpointer data)
{
pause_board(FALSE);
}
static void wordsgame_config_start(GcomprisBoard *agcomprisBoard, GcomprisProfile *aProfile)
{
if (gcomprisBoard)
pause_board(TRUE);
gchar *label = g_strdup_printf(_("%s configuration\n for profile %s"),
agcomprisBoard->name,
aProfile? aProfile->name: "");
GcomprisBoardConf *bconf;
bconf = gc_board_config_window_display( label,
(GcomprisConfCallback )conf_ok);
g_free(label);
gc_board_config_wordlist(bconf, "wordsgame/default-$LOCALE.xml");
}
static void wordsgame_config_stop(void)
{
}