/* gcompris - reading.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 .
*/
// FIXME: Cleanup of LettersItem created struct is not done
#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 GnomeCanvasGroup *boardRootItem = NULL;
typedef enum
{
MODE_HORIZONTAL = 0,
MODE_VERTICAL = 1,
MODE_HORIZONTAL_RTL = 2
} Mode;
static Mode currentMode = MODE_VERTICAL;
/* Store the moving words */
typedef struct {
GnomeCanvasGroup *rootItem;
GnomeCanvasItem *overwriteItem;
GnomeCanvasItem *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
gint current_x;
gint current_y;
gint numberOfLine;
gint font_size;
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(GnomeCanvasGroup *parent);
static gint reading_drop_items (void);
//static void reading_destroy_item(LettersItem *item);
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 GnomeCanvasItem *display_what_to_do(GnomeCanvasGroup *parent);
static void ask_ready(gboolean status);
static void ask_yes_no(void);
static gint item_event_valid(GnomeCanvasItem *item, GdkEvent *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) {
gtk_timeout_remove (drop_items_id);
drop_items_id = 0;
}
}
else
{
if(!drop_items_id) {
reading_drop_items();
}
}
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
GHashTable *config = gc_db_get_board_conf();
gc_locale_set(g_hash_table_lookup( config, "locale"));
g_hash_table_destroy(config);
if(agcomprisBoard!=NULL)
{
gchar *img;
gcomprisBoard=agcomprisBoard;
img = gc_skin_image_get("reading-bg.jpg");
gc_set_background(gnome_canvas_root(gcomprisBoard->canvas),
img);
g_free(img);
wait_for_ready = TRUE;
gamewon = FALSE;
gcomprisBoard->level = 1;
gcomprisBoard->maxlevel = 9;
gc_bar_set(GC_BAR_CONFIG|GC_BAR_LEVEL);
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_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_reset();
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_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 = GNOME_CANVAS_GROUP(
gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
gnome_canvas_group_get_type (),
"x", (double) 0,
"y", (double) 0,
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) {
gtk_timeout_remove (drop_items_id);
drop_items_id = 0;
}
if (next_level_timer) {
gtk_timeout_remove (next_level_timer);
drop_items_id = 0;
}
if(boardRootItem!=NULL)
gtk_object_destroy (GTK_OBJECT(boardRootItem));
boardRootItem = NULL;
previousFocus.rootItem = NULL;
toDeleteFocus.rootItem = NULL;
if (textToFind!=NULL)
{
g_free(textToFind);
textToFind=NULL;
}
}
static GnomeCanvasItem *display_what_to_do(GnomeCanvasGroup *parent)
{
gint base_Y = 110;
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;
gnome_canvas_item_new (parent,
gnome_canvas_text_get_type (),
"text", _("Please, check if the word"),
"font", gc_skin_font_board_medium,
"x", (double) base_X,
"y", (double) base_Y,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "black",
NULL);
gnome_canvas_item_new (parent,
gnome_canvas_text_get_type (),
"text", textToFind,
"font", gc_skin_font_board_big,
"x", (double) base_X,
"y", (double) base_Y + 30,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "green",
NULL);
gnome_canvas_item_new (parent,
gnome_canvas_text_get_type (),
"text", _("is being displayed"),
"font", gc_skin_font_board_medium,
"x", (double) base_X,
"y", (double) base_Y + 60,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "black",
NULL);
return NULL;
}
static gboolean reading_create_item(GnomeCanvasGroup *parent)
{
gint anchor = GTK_ANCHOR_CENTER;
gchar *word;
g_assert(textToFind!=NULL);
if(toDeleteFocus.rootItem)
{
gtk_object_destroy (GTK_OBJECT(toDeleteFocus.rootItem));
toDeleteFocus.rootItem = NULL;
}
if(previousFocus.rootItem)
{
gnome_canvas_item_show (previousFocus.overwriteItem);
toDeleteFocus.rootItem = previousFocus.rootItem;
}
if(numberOfLine<=0)
{
gtk_object_destroy (GTK_OBJECT(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) // the current board is finished : bail out
gc_bonus_end_display(GC_BOARD_FINISHED_RANDOM);
return FALSE;
}
if(textToFindIndex>=0)
textToFindIndex--;
previousFocus.rootItem = \
GNOME_CANVAS_GROUP( gnome_canvas_item_new (parent,
gnome_canvas_group_get_type (),
"x", (double) current_x,
"y", (double) current_y,
NULL));
if(currentMode==MODE_HORIZONTAL)
anchor=GTK_ANCHOR_WEST;
else if (currentMode==MODE_HORIZONTAL_RTL)
anchor=GTK_ANCHOR_EAST;
previousFocus.item = \
gnome_canvas_item_new (GNOME_CANVAS_GROUP(previousFocus.rootItem),
gnome_canvas_text_get_type (),
"text", word,
"font", gc_skin_font_board_medium,
"x", (double) 0,
"y", (double) 0,
"anchor", anchor,
"fill_color", "black",
NULL);
gchar *oldword = g_strdup_printf("%s", word);
g_free(word);
previousFocus.overwriteItem = \
gnome_canvas_item_new (GNOME_CANVAS_GROUP(previousFocus.rootItem),
gnome_canvas_text_get_type (),
"markup", oldword,
"font", gc_skin_font_board_medium,
"x", (double) 0,
"y", (double) 0,
"anchor", anchor,
NULL);
g_free(oldword);
gnome_canvas_item_hide(previousFocus.overwriteItem);
// Calculate the next spot
if(currentMode==MODE_VERTICAL)
{
current_y += interline;
numberOfLine--;
}
else if (currentMode==MODE_HORIZONTAL_RTL)
{
double x1, y1, x2, y2;
gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(previousFocus.rootItem), &x1, &y1, &x2, &y2);
// Are we out of bound
if(x1BASE_X2)
{
// Do the line Wrapping
gnome_canvas_item_move(GNOME_CANVAS_ITEM(previousFocus.rootItem), BASE_X1-x1, interline);
current_y += interline;
current_x = BASE_X1;
numberOfLine--;
}
current_x += x2-x1 + font_size;
}
return (TRUE);
}
/*
* This is called on a low frequency and is used to display new items
*
*/
static gint reading_drop_items ()
{
if(reading_create_item(boardRootItem))
drop_items_id = gtk_timeout_add (fallSpeed,
(GtkFunction) reading_drop_items, NULL);
return (FALSE);
}
static void ask_ready(gboolean status)
{
static GnomeCanvasItem *item1 = NULL;
static GnomeCanvasItem *item2 = NULL;
GdkPixbuf *button_pixmap = NULL;
double y_offset = 310;
double x_offset = 430;
if(textToFind==NULL)
return;
if(status==FALSE)
{
if(item1!=NULL)
gtk_object_destroy(GTK_OBJECT(item1));
if(item2!=NULL)
gtk_object_destroy(GTK_OBJECT(item2));
item1 = NULL;
item2 = NULL;
return;
}
/*----- READY -----*/
button_pixmap = gc_skin_pixmap_load("button_large2.png");
item1 = gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type (),
"pixbuf", button_pixmap,
"x", x_offset,
"y", y_offset,
NULL);
gtk_signal_connect(GTK_OBJECT(item1), "event",
(GtkSignalFunc) item_event_valid,
"R");
item2 = gnome_canvas_item_new (boardRootItem,
gnome_canvas_text_get_type (),
"text", _("I am Ready"),
"font", gc_skin_font_board_big,
"x", (double) x_offset +
gdk_pixbuf_get_width(button_pixmap)/2,
"y", (double) y_offset + 40,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "white",
NULL);
gdk_pixbuf_unref(button_pixmap);
gtk_signal_connect(GTK_OBJECT(item2), "event",
(GtkSignalFunc) item_event_valid,
"R");
}
static void ask_yes_no()
{
GnomeCanvasItem *item;
GdkPixbuf *button_pixmap = NULL;
double y_offset = 310;
double x_offset = 430;
if(textToFind==NULL)
return;
/*----- YES -----*/
button_pixmap = gc_skin_pixmap_load("button_large2.png");
item = gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type (),
"pixbuf", button_pixmap,
"x", x_offset,
"y", y_offset,
NULL);
gtk_signal_connect(GTK_OBJECT(item), "event",
(GtkSignalFunc) item_event_valid,
"Y");
item = gnome_canvas_item_new (boardRootItem,
gnome_canvas_text_get_type (),
"text", _("Yes, I saw it"),
"font", gc_skin_font_board_big,
"x", (double) x_offset +
gdk_pixbuf_get_width(button_pixmap)/2,
"y", (double) y_offset + 40,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "white",
NULL);
gtk_signal_connect(GTK_OBJECT(item), "event",
(GtkSignalFunc) item_event_valid,
"Y");
/*----- NO -----*/
y_offset += 100;
item = gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type (),
"pixbuf", button_pixmap,
"x", x_offset,
"y", y_offset,
NULL);
gtk_signal_connect(GTK_OBJECT(item), "event",
(GtkSignalFunc) item_event_valid,
"N");
item = gnome_canvas_item_new (boardRootItem,
gnome_canvas_text_get_type (),
"text", _("No, it was not there"),
"font", gc_skin_font_board_big,
"x", (double) x_offset +
gdk_pixbuf_get_width(button_pixmap)/2,
"y", (double) y_offset + 40,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "white",
NULL);
gdk_pixbuf_unref(button_pixmap);
gtk_signal_connect(GTK_OBJECT(item), "event",
(GtkSignalFunc) item_event_valid,
"N");
}
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) { // the current board is finished : bail out
gc_bonus_end_display(GC_BOARD_FINISHED_RANDOM);
return;
}
next_level_timer = g_timeout_add(3000, (GtkFunction)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, (GtkFunction)reading_next_level, NULL);
}
/* Callback for the yes and no buttons */
static gint
item_event_valid(GnomeCanvasItem *item, GdkEvent *event, gpointer data)
{
switch (event->type)
{
case GDK_ENTER_NOTIFY:
break;
case GDK_LEAVE_NOTIFY:
break;
case GDK_BUTTON_PRESS:
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;
}
break;
default:
break;
}
return FALSE;
}
/** 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);
}
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 void conf_ok(GHashTable *table)
{
if (!table){
if (gcomprisBoard)
pause_board(FALSE);
return;
}
g_hash_table_foreach(table, (GHFunc) save_table, NULL);
if (gcomprisBoard){
gc_locale_reset();
GHashTable *config;
if (profile_conf)
config = gc_db_get_board_conf();
else
config = table;
gc_locale_set(g_hash_table_lookup( config, "locale"));
if (profile_conf)
g_hash_table_destroy(config);
reading_next_level();
pause_board(FALSE);
}
board_conf = NULL;
profile_conf = NULL;
}
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");
}
/* ======================= */
/* = config_stop = */
/* ======================= */
static void
reading_config_stop()
{
}