/* gcompris - scale.c
*
* Copyright (C) 2006 Miguel de Izarra
*
* 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 "gcompris/gcompris.h"
#include // for strcmp
#define SOUNDLISTFILE PACKAGE
static GcomprisBoard *gcomprisBoard = NULL;
static gboolean board_paused = TRUE;
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 config_start(GcomprisBoard *agcomprisBoard,
GcomprisProfile *aProfile);
static void config_stop();
static void set_level (guint level);
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str);
static void process_ok(void);
static int gamewon;
static void game_won(void);
static GnomeCanvasGroup *boardRootItem = NULL;
static GnomeCanvasGroup *group_g = NULL, *group_d= NULL;
static GnomeCanvasItem *bras = NULL;
static GnomeCanvasItem *answer_item = NULL;
GString *answer_string = NULL;
static void scale_destroy_all_items(void);
static void scale_next_level(void);
#define ITEM_X_MIN 0
#define ITEM_X_MAX 500
#define ITEM_Y_MIN 250
#define ITEM_Y_MAX 400
#define ITEM_W 45
#define ITEM_H 70
#define PLATE_X1 -40.0
#define PLATE_X2 246.0
#define PLATE_Y 33.0
#define PLATE_Y_DELTA 15.0
#define PLATE_W 190.0
#define BRAS_X 55.0
#define BRAS_Y -6.0
#define PLATE_SIZE 4 // number of place in the plate
typedef struct {
GnomeCanvasItem * item;
double x, y;
int plate; // 0 not place, 1 in plate_g, -1 in plate_d
int plate_index; // position in the plate
int weight;
} ScaleItem;
static GList *item_list = NULL;
static int objet_weight = 0;
static gint drag_mode;
static const char *imageList[] = {
"gcompris/food/chocolate_cake.png",
"gcompris/food/pear.png",
"gcompris/food/orange.png",
"gcompris/food/suggar_box.png",
"gcompris/misc/flowerpot.png",
"gcompris/misc/glass.png"
};
static const int imageListCount = G_N_ELEMENTS(imageList);
static int scale_drag_event(GnomeCanvasItem *w, GdkEvent *event, ScaleItem *item);
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Scale",
"Balance the scales properly",
"Miguel de Izarra ",
NULL,
NULL,
NULL,
NULL,
start_board,
pause_board,
end_board,
is_our_board,
key_press,
process_ok,
set_level,
NULL,
NULL,
config_start,
config_stop
};
/*
* Main entry point mandatory for each Gcompris's game
* ---------------------------------------------------
*
*/
GET_BPLUGIN_INFO(scale)
/*
* in : boolean TRUE = PAUSE : FALSE = CONTINUE
*
*/
static void pause_board (gboolean pause)
{
if(gcomprisBoard==NULL)
return;
if(gamewon == TRUE && pause == FALSE) /* the game is won */
{
game_won();
}
board_paused = pause;
}
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
gcomprisBoard->level=1;
gcomprisBoard->sublevel=1;
gcomprisBoard->number_of_sublevel=5; /* Go to next level after this number of 'play' */
gcomprisBoard->maxlevel = 4;
gc_bar_set(GC_BAR_LEVEL|GC_BAR_OK|GC_BAR_CONFIG);
gamewon = FALSE;
pause_board(FALSE);
GHashTable *config = gc_db_get_board_conf();
gchar *drag_mode_str = g_hash_table_lookup( config, "drag_mode");
if (drag_mode_str && (strcmp (drag_mode_str, "NULL") != 0))
drag_mode = g_ascii_strtod(drag_mode_str, NULL);
else
drag_mode = 0;
gc_set_background(gnome_canvas_root(gcomprisBoard->canvas),
"opt/tabepice.jpg");
gc_drag_start(gnome_canvas_root(gcomprisBoard->canvas),
(gc_Drag_Func)scale_drag_event, drag_mode);
gc_score_start(SCORESTYLE_NOTE,
gcomprisBoard->width - 220,
450 ,
gcomprisBoard->number_of_sublevel);
scale_next_level();
}
}
static void end_board ()
{
if(gcomprisBoard!=NULL)
{
gc_drag_stop(gnome_canvas_root(gcomprisBoard->canvas));
pause_board(TRUE);
scale_destroy_all_items();
gc_score_end();
}
gcomprisBoard = NULL;
}
/* ======================================= */
static void set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
gcomprisBoard->sublevel=1;
scale_next_level();
}
}
/* ======================================= */
gboolean is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
if(g_strcasecmp(gcomprisBoard->type, "scale")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
return FALSE;
}
/* ======================================= */
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str)
{
if(board_paused || !boardRootItem)
return FALSE;
/* Add some filter for control and shift key */
switch (keyval)
{
/* Avoid all this keys to be interpreted by this game */
case GDK_Shift_L:
case GDK_Shift_R:
case GDK_Control_L:
case GDK_Control_R:
case GDK_Caps_Lock:
case GDK_Shift_Lock:
case GDK_Meta_L:
case GDK_Meta_R:
case GDK_Alt_L:
case GDK_Alt_R:
case GDK_Super_L:
case GDK_Super_R:
case GDK_Hyper_L:
case GDK_Hyper_R:
case GDK_Num_Lock:
return FALSE;
case GDK_KP_Enter:
case GDK_Return:
process_ok();
return TRUE;
case GDK_Right:
case GDK_Left:
break;
case GDK_BackSpace:
if(answer_string)
g_string_truncate(answer_string, answer_string->len -1);
break;
}
if(answer_string)
{
gchar *tmpstr;
gchar c = commit_str ? commit_str[0] : 0;
/* Limit the user entry to 5 digits */
if(c>='0' && c<='9' && answer_string->len < 5)
answer_string = g_string_append_c(answer_string, c);
tmpstr = g_strdup_printf(_("Weight = %s"), answer_string->str);
gnome_canvas_item_set(answer_item,
"text", tmpstr,
NULL);
g_free(tmpstr);
}
return TRUE;
}
// plate = 1 plate g
// plate = -1 plate d
// plate = 0 plate g - plate d
int get_weight_plate(int plate)
{
GList *list;
ScaleItem *item;
int result=0;
for(list = item_list; list; list=list->next)
{
item = list->data;
if(item->plate == plate || plate==0)
{
result+= item->weight * item->plate;
}
}
if(plate==-1)
result = -result;
return result;
}
static double last_delta=0;
void scale_anim_plate(void)
{
double affine[6];
double delta_y, x;
double angle;
int diff;
diff = get_weight_plate(0);
delta_y = CLAMP(PLATE_Y_DELTA / 10.0 * diff,
-PLATE_Y_DELTA, PLATE_Y_DELTA);
if(get_weight_plate(1)==0)
delta_y = -PLATE_Y_DELTA;
if(last_delta != delta_y)
{
last_delta = delta_y;
angle = tan(delta_y / 138) * 180 / M_PI;
gtk_object_get (GTK_OBJECT (group_g), "x", &x, NULL);
art_affine_translate(affine, x, delta_y);
gnome_canvas_item_affine_absolute(GNOME_CANVAS_ITEM(group_g), affine);
gtk_object_get (GTK_OBJECT (group_d), "x", &x, NULL);
art_affine_translate(affine, x, -delta_y);
gnome_canvas_item_affine_absolute(GNOME_CANVAS_ITEM(group_d), affine);
gc_item_rotate_with_center(bras, -angle, 138, 84);
}
if(diff ==0 && (gcomprisBoard->level == 2 || gcomprisBoard->level == 4))
{
GdkPixbuf *button_pixmap;
double x_offset = 40, y_offset = 150;
button_pixmap = gc_skin_pixmap_load("button_large2.png");
gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type (),
"pixbuf", button_pixmap,
"x", x_offset,
"y", y_offset,
NULL);
answer_item = gnome_canvas_item_new(boardRootItem,
gnome_canvas_text_get_type(),
"font", gc_skin_font_board_title_bold,
"x", x_offset + gdk_pixbuf_get_width(button_pixmap)/2,
"y", y_offset + gdk_pixbuf_get_height(button_pixmap)/2,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "black",
NULL);
gdk_pixbuf_unref(button_pixmap);
answer_string = g_string_new(NULL);
key_press(0,NULL,NULL);
}
}
// if plate = 1 , move to plate g
// if plate = -1, move to plate d
// if plate = 0 , move to the item list
void scale_item_move_to(ScaleItem *item, int plate)
{
ScaleItem *scale;
GList *list;
gboolean found;
int index;
if(plate!=0)
{
if(item->plate)
item->plate_index = -1;
else
gc_sound_play_ogg ("sounds/eraser1.wav", NULL);
// find the first free place in the plate
for(index=0; index < PLATE_SIZE; index ++)
{
found = FALSE;
for(list = item_list; list; list = list->next)
{
scale = list->data;
if(scale->plate_index == index && scale->plate == plate)
found=TRUE;
}
if(!found)
{ // move to the plate
item->plate = plate;
item->plate_index=index;
gnome_canvas_item_reparent(item->item, plate == 1 ? group_g : group_d);
gnome_canvas_item_set(item->item,
"x", (double)index * ITEM_W,
"y", (double)PLATE_Y-ITEM_H + 5,
NULL);
break;
}
}
if(found) // can't find place
plate=0;
}
if(plate==0)
{ // move the item to the list
if(item->plate)
gc_sound_play_ogg ("sounds/eraser1.wav", NULL);
item->plate = 0;
gnome_canvas_item_reparent(item->item, boardRootItem);
gnome_canvas_item_set(item->item,
"x", item->x,
"y", item->y, NULL);
}
scale_anim_plate();
}
static int scale_item_event(GnomeCanvasItem *w, GdkEvent *event, ScaleItem *scale)
{
if(answer_string)
return FALSE;
if(event->type == GDK_BUTTON_PRESS && event->button.button==3)
scale_item_move_to(scale, 0);
return FALSE;
}
static int scale_drag_event(GnomeCanvasItem *w, GdkEvent *event, ScaleItem *scale)
{
int plate=0;
double x,y;
if(answer_string) // disable, waiting a answer
return FALSE;
switch(event->type)
{
case GDK_BUTTON_PRESS:
gc_drag_offset_save(event);
g_object_get(G_OBJECT(scale->item), "x", &x, "y", &y, NULL);
gnome_canvas_item_i2w(scale->item, &x,&y);
gnome_canvas_item_reparent(scale->item,boardRootItem);
gnome_canvas_item_w2i(scale->item, &x, &y);
gnome_canvas_item_set(scale->item, "x", x, "y", y, NULL);
break;
case GDK_MOTION_NOTIFY:
gc_drag_item_move(event);
break;
case GDK_BUTTON_RELEASE:
x=event->button.x;
y=event->button.y;
gnome_canvas_item_w2i(GNOME_CANVAS_ITEM(group_g), &x, &y);
if(-ITEM_W < x && x < PLATE_W + ITEM_W && abs(y-PLATE_Y) < ITEM_H)
plate = 1;
else
{
x=event->button.x;
y=event->button.y;
gnome_canvas_item_w2i(GNOME_CANVAS_ITEM(group_d), &x, &y);
if(-ITEM_W < x && x < PLATE_W + ITEM_W && abs(y-PLATE_Y) < ITEM_H)
plate = -1;
else
plate=0;
}
scale_item_move_to(scale, plate);
break;
default:
break;
}
return FALSE;
}
static ScaleItem * scale_list_add_weight(gint weight)
{
ScaleItem *new_item;
GdkPixbuf *pixmap;
gchar *filename;
double x, y;
GList *last;
last=g_list_last(item_list);
if(last)
{
new_item = last->data;
x = new_item->x + ITEM_W;
y = new_item->y;
if(x > ITEM_X_MAX)
{
x = ITEM_X_MIN;
y += ITEM_H;
if(y > ITEM_Y_MAX)
g_warning("No more place for new item");
}
}
else
{
x = ITEM_X_MIN;
y = ITEM_Y_MIN;
}
new_item = g_new0(ScaleItem, 1);
new_item->x = x;
new_item->y = y;
new_item->weight = weight;
filename = g_strdup_printf("scales/masse%d.png", weight);
pixmap = gc_pixmap_load(filename);
new_item->item = gnome_canvas_item_new(boardRootItem,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap,
"x", new_item->x,
"y", new_item->y, NULL);
g_free(filename);
gdk_pixbuf_unref(pixmap);
g_signal_connect(new_item->item, "event", (GtkSignalFunc)gc_item_focus_event, NULL);
g_signal_connect(new_item->item, "event", (GtkSignalFunc)gc_drag_event, new_item);
g_signal_connect(new_item->item, "event", (GtkSignalFunc) scale_item_event, new_item);
item_list = g_list_append(item_list, new_item);
return new_item;
}
static ScaleItem * scale_list_add_object(GdkPixbuf *pixmap, int weight, int plate, gboolean show_weight)
{
GnomeCanvasItem *item;
ScaleItem * new_item;
item = gnome_canvas_item_new(group_d,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap,
"x", ((double)PLATE_SIZE * ITEM_W - gdk_pixbuf_get_width(pixmap))/2.0,
"y", PLATE_Y + 5 - gdk_pixbuf_get_height(pixmap), NULL);
gnome_canvas_item_lower_to_bottom(item);
if(show_weight)
{ // display the object weight
double x,y;
gchar * text;
x = PLATE_SIZE * ITEM_W * .5;
y = PLATE_Y - 20.0;
text = g_strdup_printf("%d", objet_weight);
gnome_canvas_item_new(group_d,
gnome_canvas_text_get_type(),
"text", text,
"font", gc_skin_font_board_medium,
"x", x + 1.0,
"y", y + 1.0,
"anchor", GTK_ANCHOR_CENTER,
"fill_color_rgba", gc_skin_color_shadow,
NULL);
gnome_canvas_item_new(group_d,
gnome_canvas_text_get_type(),
"text", text,
"font", gc_skin_font_board_medium,
"x", x,
"y", y,
"anchor", GTK_ANCHOR_CENTER,
"fill_color_rgba", gc_skin_color_content,
NULL);
g_free(text);
}
new_item = g_new0(ScaleItem, 1);
new_item->weight = weight;
new_item->plate = plate;
new_item->plate_index = -1;
new_item->item = item;
item_list = g_list_append(item_list, new_item);
return new_item;
}
// test if adding elements in table can produce total
static gboolean test_addition(int total, int *table, int len)
{
int i;
if(total == 0)
return TRUE;
for(i=0; ilevel)
{
case 1:
case 2:
objet_weight = g_random_int_range(5,20);
for(i=0;i<10; i++)
list_weight[i] = default_list_weight[i];
show_weight = gcomprisBoard->level == 1;
break;
case 3:
case 4:
while(1)
{
for(i=0; i< 5; i++)
do
tmp[i] = default_list_weight[g_random_int_range(0,10)];
while(tmp[i]==0);
objet_weight=0;
for(i=0; i<5; i++)
objet_weight += g_random_int_range(-1,2) * tmp[i];
objet_weight = abs(objet_weight);
if(!test_addition(objet_weight, tmp, 5))
break;
}
for(i=0;i<5; i++)
list_weight[i] = tmp[i];
show_weight = gcomprisBoard->level == 3;
break;
}
for(i=0; list_weight[i] ; i++)
scale_list_add_weight(list_weight[i]);
pixmap = gc_pixmap_load(imageList[g_random_int_range(0,imageListCount)]);
scale_list_add_object(pixmap, objet_weight,-1, show_weight);
gdk_pixbuf_unref(pixmap);
}
static void scale_next_level()
{
GdkPixbuf *pixmap, *pixmap2;
GnomeCanvasItem *item, *balance;
double balance_x, balance_y;
gc_bar_set_level(gcomprisBoard);
scale_destroy_all_items();
gamewon = FALSE;
gc_score_set(gcomprisBoard->sublevel);
// create the balance
pixmap = gc_pixmap_load("scales/balance.png");
balance_x = (BOARDWIDTH - gdk_pixbuf_get_width(pixmap))/2;
balance_y = (BOARDHEIGHT - gdk_pixbuf_get_height(pixmap))/2;
boardRootItem = GNOME_CANVAS_GROUP(gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
gnome_canvas_group_get_type (),
"x", balance_x,
"y", balance_y,
NULL));
balance = item = gnome_canvas_item_new(boardRootItem,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap,
"x", (double)0,
"y", (double)0,
NULL);
gdk_pixbuf_unref(pixmap);
// create plate left
group_g = GNOME_CANVAS_GROUP(gnome_canvas_item_new(boardRootItem,
gnome_canvas_group_get_type(),
"x", PLATE_X1,
"y", 0.0,
NULL));
pixmap = gc_pixmap_load("scales/plateau.png");
item = gnome_canvas_item_new(group_g,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap,
"x", 0.0, "y", PLATE_Y, NULL);
gdk_pixbuf_unref(pixmap);
// create plate right
group_d = GNOME_CANVAS_GROUP(gnome_canvas_item_new(boardRootItem,
gnome_canvas_group_get_type(),
"x", PLATE_X2,
"y", 0.0,
NULL));
pixmap = gc_pixmap_load("scales/plateau.png");
pixmap2 = gdk_pixbuf_flip(pixmap, TRUE);
item = gnome_canvas_item_new(group_d,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap2,
"x", 0.0, "y", PLATE_Y, NULL);
gdk_pixbuf_unref(pixmap);
gdk_pixbuf_unref(pixmap2);
pixmap = gc_pixmap_load("scales/bras.png");
bras = item = gnome_canvas_item_new(boardRootItem,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap,
"x", BRAS_X,
"y", BRAS_Y,
NULL);
gdk_pixbuf_unref(pixmap);
gnome_canvas_item_raise_to_top(balance);
/* display some hint */
if(gcomprisBoard->level > 2)
gnome_canvas_item_new(boardRootItem,
gnome_canvas_text_get_type(),
"text", _("Take care, you can drop masses on both sides of the scale."),
"font", gc_skin_font_board_medium,
"x", 200.0,
"y", 220.0,
"anchor", GTK_ANCHOR_CENTER,
"fill_color", "darkblue",
NULL);
scale_make_level();
last_delta=0;
scale_anim_plate();
}
static void scale_destroy_all_items()
{
GList *list;
if(boardRootItem)
gtk_object_destroy (GTK_OBJECT(boardRootItem));
boardRootItem = NULL;
if(item_list)
{
for(list = item_list; list ; list = list->next)
g_free(list->data);
g_list_free(item_list);
item_list = NULL;
}
if(answer_string)
{
g_string_free(answer_string, TRUE);
answer_string = NULL;
}
}
static void game_won()
{
gcomprisBoard->sublevel++;
if(gcomprisBoard->sublevel>gcomprisBoard->number_of_sublevel) {
/* Try the next level */
gcomprisBoard->sublevel=1;
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel) { // the current board is finished : bail out
gc_bonus_end_display(GC_BOARD_FINISHED_RANDOM);
return;
}
gc_sound_play_ogg ("sounds/bonus.wav", NULL);
}
scale_next_level();
}
/* ==================================== */
static void process_ok()
{
gboolean good_answer = TRUE;
if(board_paused)
return;
if(answer_string)
{
gint answer_weight ;
answer_weight = g_strtod(answer_string->str, NULL);
good_answer = answer_weight == objet_weight;
}
if(get_weight_plate(0)==0 && good_answer)
{
gamewon = TRUE;
scale_destroy_all_items();
gc_bonus_display(gamewon, GC_BONUS_SMILEY);
}
else
gc_bonus_display(gamewon, GC_BONUS_SMILEY);
}
/* ************************************* */
/* * 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){
GHashTable *config;
if (profile_conf)
config = gc_db_get_board_conf();
else
config = table;
gchar *drag_mode_str = g_hash_table_lookup( config, "drag_mode");
if (drag_mode_str && (g_strcasecmp (drag_mode_str, "NULL") != 0))
drag_mode = (gint ) g_ascii_strtod(drag_mode_str, NULL);
else
drag_mode = 0;
if (profile_conf)
g_hash_table_destroy(config);
gc_drag_change_mode( drag_mode);
scale_next_level();
pause_board(FALSE);
}
board_conf = NULL;
profile_conf = NULL;
}
static void
config_start(GcomprisBoard *agcomprisBoard,
GcomprisProfile *aProfile)
{
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 : "");
GcomprisBoardConf *bconf;
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 *drag_mode_str = g_hash_table_lookup( config, "drag_mode");
gint drag_previous;
if (drag_mode_str && (strcmp (drag_mode_str, "NULL") != 0))
drag_previous = (gint ) g_ascii_strtod(drag_mode_str, NULL);
else
drag_previous = 0;
gc_board_config_combo_drag(bconf, drag_mode);
}
/* ======================= */
/* = config_stop = */
/* ======================= */
static void config_stop()
{
}