/* gcompris - reading.c
*
* Copyright (C) 2000, 2008 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"
#define SOUNDLISTFILE PACKAGE
#define MAXWORDSLENGTH 50
#define MAX_WORDS 100
static GcomprisWordlist *gc_wordlist = NULL;
static GcomprisBoard *gcomprisBoard = NULL;
static gint drop_items_id = 0;
static gint next_level_timer = 0;
static gchar *textToFind = NULL;
static gint textToFindIndex = 0;
#define NOT_THERE -1000
static GooCanvasItem *boardRootItem = NULL;
static gboolean uppercase_only;
typedef enum
{
MODE_HORIZONTAL = 0,
MODE_VERTICAL = 1,
MODE_HORIZONTAL_RTL = 2
} Mode;
static Mode currentMode = MODE_VERTICAL;
/* Store the moving words */
typedef struct {
GooCanvasItem *rootItem;
GooCanvasItem *overwriteItem;
GooCanvasItem *item;
} LettersItem;
static LettersItem previousFocus;
static LettersItem toDeleteFocus;
/* Define the page area where text can be displayed */
#define BASE_X1 70
#define BASE_Y1 120
#define BASE_X2 350
#define BASE_Y2 520
#define BASE_CX BASE_X1+(BASE_X2-BASE_X1)/2
static gint current_x;
static gint current_y;
static gint numberOfLine;
static gint font_size;
static gint interline;
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 int wait_for_ready;
static int gamewon;
static gboolean reading_create_item(GooCanvasItem *parent);
static gboolean reading_drop_items (gpointer data);
static void reading_destroy_all_items(void);
static gint reading_next_level(void);
static void reading_config_start(GcomprisBoard *agcomprisBoard,
GcomprisProfile *aProfile);
static void reading_config_stop(void);
static void player_win(void);
static void player_loose(void);
static gchar *get_random_word(const gchar *except);
static GooCanvasItem *display_what_to_do(GooCanvasItem *parent);
static void ask_ready(gboolean status);
static void ask_yes_no(void);
static gboolean item_event_valid (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data);
static guint32 fallSpeed = 0;
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Reading",
"Read a list of words and then work out if the given word is in it",
"Bruno Coudoin ",
NULL,
NULL,
NULL,
NULL,
start_board,
pause_board,
end_board,
is_our_board,
NULL,
NULL,
set_level,
NULL,
NULL,
reading_config_start,
reading_config_stop
};
/*
* Main entry point mandatory for each Gcompris's game
* ---------------------------------------------------
*
*/
GET_BPLUGIN_INFO(reading)
/*
* in : boolean TRUE = PAUSE : FALSE = UNPAUSE
*
*/
static void pause_board (gboolean pause)
{
// after the bonus is ended, the board is unpaused, but we must wait for
// the player to be ready (this board does not use the same framework as others)
if (wait_for_ready)
return;
if(gcomprisBoard==NULL)
return;
if(pause)
{
if (drop_items_id) {
g_source_remove (drop_items_id);
drop_items_id = 0;
}
}
else
{
if(!drop_items_id) {
reading_drop_items(NULL);
}
}
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
GHashTable *config = gc_db_get_board_conf();
gc_locale_set(g_hash_table_lookup( config, "locale"));
gchar *up_init_str = g_hash_table_lookup( config, "uppercase_only");
if (up_init_str && (strcmp(up_init_str, "True")==0))
uppercase_only = TRUE;
else
uppercase_only = FALSE;
g_hash_table_destroy(config);
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
"readingh/reading-bg.svgz");
wait_for_ready = TRUE;
gamewon = FALSE;
gcomprisBoard->level = 1;
gcomprisBoard->maxlevel = 9;
gc_bar_set(GC_BAR_CONFIG|GC_BAR_LEVEL);
gc_bar_location(BOARDWIDTH-240, -1, 0.7);
PangoFontDescription *font_medium = pango_font_description_from_string(gc_skin_font_board_medium);
font_size = PANGO_PIXELS(pango_font_description_get_size (font_medium));
interline = (int) (1.5*font_size);
PangoContext *pango_context = gtk_widget_get_pango_context (GTK_WIDGET(agcomprisBoard->canvas));
PangoFontMetrics* pango_metrics = pango_context_get_metrics (pango_context,
font_medium,
pango_language_from_string (gc_locale_get()));
pango_font_description_free(font_medium);
int ascent = PANGO_PIXELS(pango_font_metrics_get_ascent (pango_metrics));
int descent = PANGO_PIXELS(pango_font_metrics_get_descent (pango_metrics));
pango_font_metrics_unref(pango_metrics);
interline = ascent + descent;
g_warning ("Font to display words have size %d ascent : %d, descent : %d.\n Set inerline to %d",
font_size, ascent, descent, interline);
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;
}
}
currentMode=MODE_VERTICAL; // Default mode
if(gcomprisBoard->mode && g_ascii_strcasecmp(gcomprisBoard->mode, "horizontal")==0)
{
if (pango_unichar_direction(g_utf8_get_char(gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level))) == PANGO_DIRECTION_RTL)
currentMode=MODE_HORIZONTAL_RTL;
else
currentMode=MODE_HORIZONTAL;
}
reading_next_level();
}
}
static void
end_board ()
{
if(gcomprisBoard!=NULL)
{
pause_board(TRUE);
reading_destroy_all_items();
}
if (gc_wordlist != NULL){
gc_wordlist_free(gc_wordlist);
gc_wordlist = NULL;
}
gc_locale_set( NULL );
gcomprisBoard = NULL;
}
static void
set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
reading_next_level();
}
}
gboolean
is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_ascii_strcasecmp(gcomprisBoard->type, "reading")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static gint reading_next_level()
{
gc_bar_set_level(gcomprisBoard);
gamewon = FALSE;
reading_destroy_all_items();
boardRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas),
NULL);
/* Default speed */
fallSpeed = 1400-gcomprisBoard->level*120;
if(currentMode==MODE_VERTICAL)
{
current_x = BASE_CX;
numberOfLine = 7 + gcomprisBoard->level;
}
else
{
current_x = BASE_X2;
numberOfLine = 2 + gcomprisBoard->level;
}
current_y = BASE_Y1 - 2 * interline;
gcomprisBoard->number_of_sublevel = 1;
gcomprisBoard->sublevel = 1;
display_what_to_do(boardRootItem);
ask_ready(TRUE);
return (FALSE);
}
/* Destroy all the items */
static void
reading_destroy_all_items()
{
if (drop_items_id) {
g_source_remove (drop_items_id);
drop_items_id = 0;
}
if (next_level_timer) {
g_source_remove (next_level_timer);
drop_items_id = 0;
}
if(boardRootItem!=NULL)
goo_canvas_item_remove(boardRootItem);
boardRootItem = NULL;
previousFocus.rootItem = NULL;
toDeleteFocus.rootItem = NULL;
if (textToFind!=NULL)
{
g_free(textToFind);
textToFind=NULL;
}
}
static GooCanvasItem *
display_what_to_do(GooCanvasItem *parent)
{
gint base_Y = 90;
gint base_X = 570;
/* Load the text to find */
textToFind = get_random_word(NULL);
g_assert(textToFind != NULL);
/* Decide now if this time we will display the text to find */
/* Use this formula to have a better random number see 'man 3 rand' */
if(g_random_boolean())
textToFindIndex = g_random_int_range(0, numberOfLine);
else
textToFindIndex = NOT_THERE;
goo_canvas_text_new (parent,
_("Please, check if the word"),
(double) base_X,
(double) base_Y,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_medium,
"fill-color", "black",
NULL);
goo_canvas_text_new (parent,
textToFind,
(double) base_X,
(double) base_Y + 30,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_big,
"fill-color", "blue",
NULL);
goo_canvas_text_new (parent,
_("is being displayed"),
(double) base_X,
(double) base_Y + 60,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_medium,
"fill-color", "black",
NULL);
return NULL;
}
static gboolean
reading_create_item(GooCanvasItem *parent)
{
gint anchor = GTK_ANCHOR_CENTER;
gchar *word;
g_assert(textToFind!=NULL);
if(toDeleteFocus.rootItem)
{
goo_canvas_item_remove(toDeleteFocus.rootItem);
toDeleteFocus.rootItem = NULL;
}
if(previousFocus.rootItem)
{
g_object_set (previousFocus.overwriteItem,
"visibility", GOO_CANVAS_ITEM_VISIBLE,
NULL);
toDeleteFocus.rootItem = previousFocus.rootItem;
}
if(numberOfLine<=0)
{
goo_canvas_item_remove(toDeleteFocus.rootItem);
toDeleteFocus.rootItem = NULL;
ask_yes_no();
return FALSE;
}
if(textToFindIndex!=0)
{
word = get_random_word(textToFind);
}
else
{
word = g_strdup(textToFind);
}
if(word==NULL)
{
gc_dialog(_("We skip this level because there are not enough words in the list!"),
(DialogBoxCallBack)reading_next_level);
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
gcomprisBoard->level = gcomprisBoard->maxlevel;
return FALSE;
}
if(textToFindIndex>=0)
textToFindIndex--;
previousFocus.rootItem = \
goo_canvas_group_new (parent,
NULL);
goo_canvas_item_translate(previousFocus.rootItem,
current_x,
current_y);
if(currentMode==MODE_HORIZONTAL)
anchor=GTK_ANCHOR_WEST;
else if (currentMode==MODE_HORIZONTAL_RTL)
anchor=GTK_ANCHOR_EAST;
previousFocus.item = \
goo_canvas_text_new (previousFocus.rootItem,
word,
(double) 0,
(double) 0,
-1,
anchor,
"font", gc_skin_font_board_medium,
"fill-color", "black",
NULL);
gchar *oldword = g_strdup_printf("%s", word);
g_free(word);
previousFocus.overwriteItem = \
goo_canvas_text_new (previousFocus.rootItem,
oldword,
0,
0,
-1,
anchor,
"font", gc_skin_font_board_medium,
"use-markup", TRUE,
NULL);
g_free(oldword);
g_object_set (previousFocus.overwriteItem,
"visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
// Calculate the next spot
if(currentMode==MODE_VERTICAL)
{
current_y += interline;
numberOfLine--;
}
else if (currentMode==MODE_HORIZONTAL_RTL)
{
GooCanvasBounds bounds;
goo_canvas_item_get_bounds(previousFocus.rootItem, &bounds);
// Are we out of bound
if(bounds.x1 BASE_X2)
{
// Do the line Wrapping
goo_canvas_item_translate(previousFocus.rootItem,
BASE_X1-bounds.x1, interline);
current_y += interline;
current_x = BASE_X1;
numberOfLine--;
}
current_x += bounds.x2 - bounds.x1 + font_size;
}
return (TRUE);
}
/*
* This is called on a low frequency and is used to display new items
*
*/
static gboolean
reading_drop_items (gpointer data)
{
if(reading_create_item(boardRootItem))
drop_items_id = g_timeout_add (fallSpeed,
reading_drop_items, NULL);
return (FALSE);
}
static GooCanvasItem *
addBackground(GooCanvasItem *parent,
GooCanvasItem *item)
{
GooCanvasBounds bounds;
int gap = 8;
goo_canvas_item_get_bounds (item, &bounds);
return(goo_canvas_rect_new (parent,
bounds.x1 - gap,
bounds.y1 - gap,
bounds.x2 - bounds.x1 + gap*2,
bounds.y2 - bounds.y1 + gap*2,
"stroke_color_rgba", 0xFFFFFFFFL,
"fill_color_rgba", 0XD5C393FFL,
"line-width", (double) 2,
"radius-x", (double) 10,
"radius-y", (double) 10,
NULL) );
}
static void
ask_ready(gboolean status)
{
static GooCanvasItem *item1 = NULL;
static GooCanvasItem *item2 = NULL;
double x_offset = 560;
double y_offset = 260;
if(textToFind==NULL)
return;
if(status==FALSE)
{
gc_item_focus_remove(item1, NULL);
gc_item_focus_remove(item2, item1);
if(item1!=NULL)
goo_canvas_item_remove(item1);
if(item2!=NULL)
goo_canvas_item_remove(item2);
item1 = NULL;
item2 = NULL;
return;
}
/*----- READY -----*/
item2 = goo_canvas_text_new (boardRootItem,
_("I am Ready"),
x_offset,
y_offset,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_big,
"fill-color", "white",
NULL);
g_signal_connect(item2, "button-press-event",
(GCallback) item_event_valid,
"R");
item1 = addBackground(boardRootItem, item2);
g_signal_connect(item1, "button-press-event",
(GCallback) item_event_valid,
"R");
gc_item_focus_init(item1, NULL);
gc_item_focus_init(item2, item1);
goo_canvas_item_raise(item2, NULL);
}
static void
ask_yes_no()
{
GooCanvasItem *item1;
GooCanvasItem *item2;
double x_offset = 560;
double y_offset = 260;
if(textToFind==NULL)
return;
/*----- YES -----*/
item2 =
goo_canvas_text_new (boardRootItem,
_("Yes, I saw it"),
x_offset,
y_offset,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_big,
"fill-color", "white",
NULL);
item1 = addBackground(boardRootItem, item2);
g_signal_connect(item2, "button-press-event",
(GCallback) item_event_valid,
"Y");
g_signal_connect(item1, "button-press-event",
(GCallback) item_event_valid,
"Y");
gc_item_focus_init(item1, NULL);
gc_item_focus_init(item2, item1);
goo_canvas_item_raise(item2, NULL);
/*----- NO -----*/
y_offset += 100;
item2 =
goo_canvas_text_new (boardRootItem,
_("No, it was not there"),
x_offset,
y_offset,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_big,
"fill-color", "white",
NULL);
item1 = addBackground(boardRootItem, item2);
g_signal_connect(item2, "button-press-event",
(GCallback) item_event_valid,
"N");
g_signal_connect(item1, "button-press-event",
(GCallback) item_event_valid,
"N");
gc_item_focus_init(item1, NULL);
gc_item_focus_init(item2, item1);
goo_canvas_item_raise(item2, NULL);
}
static void
player_win()
{
gamewon = TRUE;
wait_for_ready = TRUE;
gc_bonus_display(gamewon, GC_BONUS_FLOWER);
/* Try the next level */
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
gcomprisBoard->level = gcomprisBoard->maxlevel;
next_level_timer = g_timeout_add(3000, (GSourceFunc)reading_next_level, NULL);
}
static void
player_loose()
{
gchar *expected;
gchar *got;
gamewon = FALSE;
wait_for_ready = TRUE;
/* Report what was wrong in the log */
expected = g_strdup_printf(_("The word to find was '%s'"), textToFind);
if(textToFindIndex == NOT_THERE)
got = g_strdup_printf(_("But it was not displayed"));
else
got = g_strdup_printf(_("And it was displayed"));
gc_log_set_comment (gcomprisBoard, expected, got);
g_free(expected);
g_free(got);
gc_bonus_display(gamewon, GC_BONUS_FLOWER);
next_level_timer = g_timeout_add(3000, (GSourceFunc)reading_next_level, NULL);
}
/* Callback for the yes and no buttons */
static gboolean
item_event_valid (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data)
{
if (((char *)data)[0]=='R')
{
// The user is Ready
wait_for_ready = FALSE;
ask_ready(FALSE);
pause_board(FALSE);
}
else if(!wait_for_ready) {
if ((((char *)data)[0]=='Y' && textToFindIndex == -1)
|| (((char *)data)[0]=='N' && textToFindIndex == NOT_THERE))
{
player_win();
}
else
{
player_loose();
}
}
return TRUE;
}
/** Return a random word from a set of text file depending on
* the current level and language
*
* \param except: if non NULL, never return this value
*
* \return a random word from. must be freed by the caller
*/
static gchar *
get_random_word(const gchar* except)
{
gchar *word;
int count=0;
word = gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level);
if(except)
while(strcmp(except, word)==0)
{
g_free(word);
if(count++>100)
{
word = NULL;
break;
}
word = gc_wordlist_random_word_get(gc_wordlist, gcomprisBoard->level);
}
if (word && uppercase_only)
{
gchar *old = word;
word = g_utf8_strup(old, -1);
g_free(old);
}
return(word);
}
/* ************************************* */
/* * Configuration * */
/* ************************************* */
/* ======================= */
/* = config_start = */
/* ======================= */
static GcomprisProfile *profile_conf;
static GcomprisBoard *board_conf;
static void save_table (gpointer key,
gpointer value,
gpointer user_data)
{
gc_db_set_board_conf ( profile_conf,
board_conf,
(gchar *) key,
(gchar *) value);
}
static gboolean
conf_ok(GHashTable *table)
{
if (!table){
if (gcomprisBoard)
pause_board(FALSE);
return TRUE;
}
g_hash_table_foreach(table, (GHFunc) save_table, NULL);
if (gcomprisBoard){
GHashTable *config;
if (profile_conf)
config = gc_db_get_board_conf();
else
config = table;
gc_locale_set(g_hash_table_lookup( config, "locale"));
gchar *up_init_str = g_hash_table_lookup( config, "uppercase_only");
if (up_init_str)
{
if(strcmp(up_init_str, "True")==0)
uppercase_only = TRUE;
else
uppercase_only = FALSE;
}
if (profile_conf)
g_hash_table_destroy(config);
reading_next_level();
pause_board(FALSE);
}
board_conf = NULL;
profile_conf = NULL;
return TRUE;
}
static void
reading_config_start(GcomprisBoard *agcomprisBoard,
GcomprisProfile *aProfile)
{
GcomprisBoardConf *conf;
board_conf = agcomprisBoard;
profile_conf = aProfile;
if (gcomprisBoard)
pause_board(TRUE);
gchar *label = g_strdup_printf(_("%s configuration\n for profile %s"),
agcomprisBoard->name,
aProfile? aProfile->name: "");
conf = gc_board_config_window_display( label,
(GcomprisConfCallback )conf_ok);
g_free(label);
/* init the combo to previously saved value */
GHashTable *config = gc_db_get_conf( profile_conf, board_conf);
gchar *locale = g_hash_table_lookup( config, "locale");
gc_board_config_combo_locales(conf, locale);
gc_board_config_wordlist(conf, "wordsgame/default-$LOCALE.xml");
/* upper case */
gboolean up_init = FALSE;
gchar *up_init_str = g_hash_table_lookup( config, "uppercase_only");
if (up_init_str && (strcmp(up_init_str, "True")==0))
up_init = TRUE;
gc_board_config_boolean_box(conf, _("Uppercase only text"),
"uppercase_only",
up_init);
}
/* ======================= */
/* = config_stop = */
/* ======================= */
static void
reading_config_stop()
{
}