/* gcompris - gletters.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 MAX_RAND_ATTEMPTS 5
static GList *item_list = NULL;
static GList *item2del_list = NULL;
static guint actors_count = 0;
static GcomprisBoard *gcomprisBoard = NULL;
static gint dummy_id = 0;
static gint drop_items_id = 0;
/* Sublevels are now allocated dynamically
* based on the number of chars at that level
* Set DEFAULT_SUBLEVEL to the minimum
* number of sublevels you want
*/
#define DEFAULT_SUBLEVEL 8
/* these constants control how fast letters fall
* the base rate is fixed
* the increment governs increase per level
* the smaller the numbers, the faster the letters fall
*/
#define FALL_RATE_BASE 40
static float fallRateBase = FALL_RATE_BASE;
#define FALL_RATE_MULT 100
static float fallRateMult = FALL_RATE_MULT;
/* these constants control how often letters are dropped
* the base rate is fixed
* the increment governs increase per level
*/
#define DROP_RATE_BASE 1000
static float dropRateBase = DROP_RATE_BASE;
#define DROP_RATE_MULT 8000
static float dropRateMult = DROP_RATE_MULT;
/* both letters_array and keymap are read in
* dynamically at run-time from files based on
* user locale
*/
/* letters_array contains letters you want shown
* on each play level
* there can be an arbitrary number of levels,
* but there are only graphics to level 9
* so that's where we stop
*/
#define MAXLEVEL 10
static int maxLevel;
static char *letters_array[MAXLEVEL];
/* keymap contains pairs of chars. The first char is
* on the keyboard map, the second is the unichar that
* is also represented by that key. That way, if there is more
* than one character represented by a key, the user doesn't
* have to use alternate input methods.
* It turns out that some keyboards generate long unichars,
* so keymap has to be big enough for 2 unichars
* both chars are packed into the same string; this makes it
* easier to deal with.
*/
static int keyMapSize;
static char **keyMap;
/* Hash table of all displayed letters */
static GHashTable *letters_table= NULL;
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 void gletter_config_start(GcomprisBoard *agcomprisBoard,
GcomprisProfile *aProfile);
static void gletter_config_stop(void);
static GooCanvasItem *gletters_create_item(GooCanvasItem *parent);
static gboolean gletters_drop_items (gpointer data);
static gboolean gletters_move_items (gpointer data);
static void gletters_destroy_item(GooCanvasItem *item);
static void gletters_destroy_items(void);
static void gletters_destroy_all_items(void);
static void gletters_next_level(void);
static void gletters_add_new_item(void);
static void player_win(GooCanvasItem *item);
static void player_loose(void);
static GooCanvasItem *item_find_by_title (const gunichar *title);
static gunichar *key_find_by_item (const GooCanvasItem *item);
static guint32 fallSpeed = 0;
static double speed = 0.0;
static int gamewon;
static gboolean with_sound = FALSE;
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
N_("Simple Letters"),
N_("Type the falling letters 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,
gletter_config_start,
gletter_config_stop
};
/*
* Main entry point mandatory for each Gcompris's game
* ---------------------------------------------------
*
*/
GET_BPLUGIN_INFO(gletters)
/*
* in : boolean TRUE = PAUSE : FALSE = UNPAUSE
*
*/
static void level_set_score() {
int l;
g_message("letters_array length for level %d is %ld\n",
gcomprisBoard->level,
g_utf8_strlen(letters_array[gcomprisBoard->level-1],-1));
l = g_utf8_strlen(letters_array[gcomprisBoard->level-1],-1)/3;
gcomprisBoard->number_of_sublevel = (DEFAULT_SUBLEVEL>l?DEFAULT_SUBLEVEL:l);
gc_score_start(SCORESTYLE_NOTE,
BOARDWIDTH - 195,
BOARDHEIGHT - 30,
gcomprisBoard->number_of_sublevel);
gc_bar_set(GC_BAR_CONFIG|GC_BAR_LEVEL);
}
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(gamewon == TRUE) /* the game is won */
{
level_set_score();
gletters_next_level();
}
if(!drop_items_id) {
drop_items_id = g_timeout_add (1000,
gletters_drop_items, NULL);
}
if(!dummy_id) {
dummy_id = g_timeout_add (1000, gletters_move_items, NULL);
}
}
}
static gboolean uppercase_only;
int load_default_charset() {
g_message("in load_default_charset\n");
gchar *numbers;
gchar *alphabet_lowercase;
gchar *alphabet_uppercase;
/* TRANSLATORS: Put here the numbers in your language */
numbers=_("0123456789");
g_assert(g_utf8_validate(numbers,-1,NULL)); // require by all utf8-functions
/* TRANSLATORS: Put here the alphabet lowercase in your language */
alphabet_lowercase=_("abcdefghijklmnopqrstuvwxyz");
g_assert(g_utf8_validate(alphabet_lowercase,-1,NULL)); // require by all utf8-functions
g_warning("Using lowercase %s", alphabet_lowercase);
/* TRANSLATORS: Put here the alphabet uppercase in your language */
alphabet_uppercase=_("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
g_assert(g_utf8_validate(alphabet_uppercase,-1,NULL)); // require by all utf8-functions
g_warning("Using uppercase %s", alphabet_uppercase);
letters_array[0] = g_strdup(alphabet_uppercase);
letters_array[1] = g_strdup_printf("%s%s",
alphabet_uppercase,
numbers);
if (!uppercase_only){
letters_array[2] = g_strdup(alphabet_lowercase);
letters_array[3] = g_strdup_printf("%s%s",
alphabet_lowercase,
numbers);
letters_array[4] = g_strdup_printf("%s%s",
alphabet_lowercase,
alphabet_uppercase);
letters_array[5] = g_strdup_printf("%s%s%s",
alphabet_lowercase,
alphabet_uppercase,
numbers);
} else{
g_warning("Uppercase only is set");
letters_array[2] = g_strdup(alphabet_uppercase);
letters_array[3] = g_strdup_printf("%s%s",
alphabet_uppercase,
numbers);
letters_array[4] = g_strdup_printf("%s%s",
alphabet_uppercase,
numbers);
letters_array[5] = g_strdup_printf("%s%s",
alphabet_uppercase,
numbers);
}
keyMapSize = 0;
maxLevel = 6;
return TRUE;
}
int whitespace(char *buffer) {
int i;
i = 0;
while(buffer[i] != '\0') {
if(buffer[i] == ' ' || buffer[i] == '\t' || buffer[i++] == '\n')
continue;
else return FALSE;
}
return TRUE;
}
/*
*/
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;
gchar *control_sound = g_hash_table_lookup( config, "with_sound");
if (control_sound && strcmp(g_hash_table_lookup( config, "with_sound"),"True")==0)
with_sound = TRUE;
else
with_sound = FALSE;
g_hash_table_destroy(config);
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
load_default_charset();
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
"gletters/scenery_background.png");
gcomprisBoard->maxlevel=maxLevel;
gcomprisBoard->level = 1;
level_set_score();
gletters_next_level();
gamewon = FALSE;
pause_board(FALSE);
}
}
static void
end_board ()
{
int i;
if(gcomprisBoard!=NULL)
{
pause_board(TRUE);
gc_score_end();
gletters_destroy_all_items();
g_message("freeing memory");
for (i = 0; i < maxLevel; i++)
g_free(letters_array[i]);
for (i = 0; i < keyMapSize; i++)
g_free(keyMap[i]);
g_free(keyMap);
}
gc_locale_set( NULL );
gcomprisBoard = NULL;
}
static void
set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
level_set_score();
gletters_next_level();
}
}
/* Append in char_list one of the falling letter */
static void add_char(char *key, char *value, char *char_list)
{
strcat(char_list, key);
}
gboolean unichar_comp(gpointer key,
gpointer value,
gpointer user_data)
{
gunichar *target = (gunichar *) user_data;
if (*((gunichar *)key) == *target)
return TRUE;
return FALSE;
}
gint is_falling_letter(gunichar unichar)
{
GooCanvasItem *item;
if ((item = g_hash_table_find(letters_table,
unichar_comp,
&unichar)))
{
player_win(item);
return TRUE;
}
return FALSE;
}
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str) {
gint length_passed, i;
gunichar c;
gchar list_of_letters[255];
gchar *str;
if(!gcomprisBoard)
return FALSE;
/* i suppose even numbers are passed through IM_context */
if ((commit_str == NULL) && (preedit_str == NULL))
return FALSE;
gchar *string_passed;
if (commit_str)
string_passed = commit_str;
else
string_passed = preedit_str;
str = g_strdup(string_passed);
length_passed = g_utf8_strlen(string_passed, -1);
for (i=0; i < length_passed; i++){
c = g_utf8_get_char (string_passed);
if (is_falling_letter(c)){
gc_im_reset();
return TRUE;
}
/* if uppercase_only is set we do not care about upper or lower case at all */
gint level_uppercase;
if (uppercase_only)
level_uppercase = 10;
else
level_uppercase = 3;
/* for 2 (or all) first level don't care abour uppercase/lowercase */
if ((gcomprisBoard->level < level_uppercase) &&
(is_falling_letter(g_unichar_toupper(c)))){
gc_im_reset();
return TRUE;
}
string_passed = g_utf8_next_char (string_passed);
}
list_of_letters[0] = '\0';
/* We have to loop to concat the letters */
g_hash_table_foreach (letters_table,
(GHFunc) add_char,
list_of_letters);
/* Log what happened, what was expected, what we got */
gc_log_set_comment(gcomprisBoard, list_of_letters, str);
g_free(str);
return TRUE;
}
static gboolean
is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_ascii_strcasecmp(gcomprisBoard->type, "gletters")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void gletters_next_level()
{
gamewon = FALSE;
gc_bar_set_level(gcomprisBoard);
gletters_destroy_all_items();
/* Try the next level */
speed=fallRateBase+(fallRateMult/gcomprisBoard->level);
fallSpeed=dropRateBase+(dropRateMult/gcomprisBoard->level);
gcomprisBoard->sublevel=1;
gc_score_set(gcomprisBoard->sublevel);
}
static void gletters_move_item(GooCanvasItem *item)
{
GooCanvasBounds bounds;
goo_canvas_item_translate(item, 0, 2.0);
goo_canvas_item_get_bounds (item,
&bounds);
if(bounds.y1>BOARDHEIGHT) {
item2del_list = g_list_append (item2del_list, item);
player_loose();
}
}
static void gletters_destroy_item(GooCanvasItem *item)
{
gunichar *key;
key = key_find_by_item(item);
item_list = g_list_remove (item_list, item);
--actors_count;
item2del_list = g_list_remove (item2del_list, item);
/* Remove old letter; this destroy the canvas item */
g_hash_table_remove (letters_table, key);
}
/* Destroy items that falls out of the canvas */
static void gletters_destroy_items()
{
GooCanvasItem *item;
while(g_list_length(item2del_list)>0)
{
item = g_list_nth_data(item2del_list, 0);
gletters_destroy_item(item);
}
}
/* Destroy all the items */
static void gletters_destroy_all_items()
{
GooCanvasItem *item;
if(item_list)
while(g_list_length(item_list)>0)
{
item = g_list_nth_data(item_list, 0);
gletters_destroy_item(item);
}
actors_count= 0;
/* Delete the letters_table */
if(letters_table) {
g_hash_table_destroy (letters_table);
letters_table=NULL;
}
}
/*
* This does the moves of the game items on the play canvas
*
*/
static gboolean gletters_move_items (gpointer data)
{
g_list_foreach (item_list, (GFunc) gletters_move_item, NULL);
/* Destroy items that falls out of the canvas */
gletters_destroy_items();
dummy_id = g_timeout_add (gc_timing (speed, actors_count),
gletters_move_items, NULL);
return(FALSE);
}
void destroy_canvas_item(gpointer item)
{
//g_free(g_object_get_data (G_OBJECT(item),"unichar_key"));
//g_free(g_object_get_data (G_OBJECT(item),"utf8_key"));
goo_canvas_item_remove(item);
}
static GooCanvasItem *gletters_create_item(GooCanvasItem *parent)
{
GooCanvasItem *item;
gint i,j,k;
guint x;
gunichar *lettersItem;
gchar *str_p, *letter;
if (!letters_table)
{
letters_table = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, destroy_canvas_item);
}
/* Beware, since we put the letters in a hash table, we do not allow the same
* letter to be displayed two times
*/
g_warning("dump: %d, %s\n",gcomprisBoard->level,letters_array[gcomprisBoard->level-1]);
k = g_utf8_strlen(letters_array[gcomprisBoard->level-1],-1);
lettersItem = g_new(gunichar,1);
gint attempt=0;
do
{
attempt++;
str_p = letters_array[gcomprisBoard->level-1];
i = g_random_int_range(0,k);
for(j = 0; j < i; j++)
{
str_p=g_utf8_next_char(str_p);
}
*lettersItem = g_utf8_get_char (str_p);
} while((attemptcanvas));
}
/*
* This is called on a low frequency and is used to drop new items
*
*/
static gboolean gletters_drop_items (gpointer data)
{
gc_sound_play_ogg ("sounds/level.wav", NULL);
gletters_add_new_item();
drop_items_id = g_timeout_add (fallSpeed,
gletters_drop_items, NULL);
return (FALSE);
}
static void player_win(GooCanvasItem *item)
{
gletters_destroy_item(item);
gc_sound_play_ogg ("sounds/flip.wav", NULL);
gcomprisBoard->sublevel++;
if(gcomprisBoard->sublevel > gcomprisBoard->number_of_sublevel)
{
/* Try the next level */
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
gcomprisBoard->level = gcomprisBoard->maxlevel;
gamewon = TRUE;
gletters_destroy_all_items();
gc_bonus_display(gamewon, GC_BONUS_SMILEY);
}
else
{
gc_score_set(gcomprisBoard->sublevel);
/* Drop a new item now to speed up the game */
if(g_list_length(item_list)==0)
{
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,
gletters_drop_items, NULL);
}
}
}
}
static void player_loose()
{
gc_sound_play_ogg ("sounds/crash.wav", NULL);
}
static gunichar *
key_find_by_item (const GooCanvasItem *item)
{
return g_object_get_data (G_OBJECT(item), "unichar_key");
}
static GooCanvasItem *
item_find_by_title (const gunichar *title)
{
if (!letters_table)
return NULL;
return g_hash_table_lookup (letters_table, title);
}
/***********************************
************************************
Config
************************************/
/* ======================= */
/* = 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);
}
/* a GcomprisConfCallback
*/
static gboolean conf_ok(GHashTable *table)
{
if (!table){
if (gcomprisBoard)
pause_board(FALSE);
return TRUE;
}
g_hash_table_foreach(table, 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 && (strcmp(up_init_str, "True")==0))
uppercase_only = TRUE;
else
uppercase_only = FALSE;
gchar *control_sound = g_hash_table_lookup( config, "with_sound");
if (control_sound && strcmp(g_hash_table_lookup( config, "with_sound"),"True")==0)
with_sound = TRUE;
else
with_sound = FALSE;
if (profile_conf)
g_hash_table_destroy(config);
load_default_charset();
level_set_score();
gletters_next_level();
pause_board(FALSE);
}
board_conf = NULL;
profile_conf = NULL;
return TRUE;
}
static void
gletter_config_start(GcomprisBoard *agcomprisBoard,
GcomprisProfile *aProfile)
{
board_conf = agcomprisBoard;
profile_conf = aProfile;
gchar *label;
if (gcomprisBoard)
pause_board(TRUE);
label = g_strdup_printf(_("%s configuration\n for profile %s"),
agcomprisBoard->name, aProfile ? aProfile->name : "");
GcomprisBoardConf *bconf = 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( bconf, locale);
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_conf_separator(bconf);
gchar *control_sound = g_hash_table_lookup( config, "with_sound");
if (control_sound && strcmp(g_hash_table_lookup( config, "with_sound"),"True")==0)
with_sound = TRUE;
else
with_sound = FALSE;
gc_board_config_boolean_box(bconf, _("Enable sounds"), "with_sound", with_sound);
gc_board_conf_separator(bconf);
gc_board_config_boolean_box(bconf, _("Uppercase only text"),
"uppercase_only",
up_init);
}
/* ======================= */
/* = config_stop = */
/* ======================= */
static void
gletter_config_stop()
{
}