/* gcompris - erase.c
*
* Copyright (C) 2001, 2008, 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_LAYERS 3
typedef struct {gint count; gint max;} counter;
static GcomprisBoard *gcomprisBoard = NULL;
static gboolean board_paused = TRUE;
static RsvgHandle *CoverPixmap[MAX_LAYERS];
static gulong event_handle_id;
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 gamewon;
static void game_won(void);
static void set_cursor();
static GooCanvasItem *boardRootItem = NULL;
static GooCanvasItem *erase_create_item();
static void erase_destroy_all_items(void);
static void erase_next_level(void);
static gboolean item_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventCrossing *event,
gpointer data);
static gboolean canvas_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data);
static void init_user_dir();
static void load_image_from_dir(char *image_dir,
GSList **image_list);
static int number_of_items = 0;
static int number_of_item_x = 0;
static int number_of_item_y = 0;
static int *items_per_cell = 0;
static guint normal_delay_id = 0;
static gint timer_id = 0;
#define NORMAL 0
#define CLIC 1
#define DOUBLECLIC 2
static gint board_mode = NORMAL;
// List of images to use in the game
static GSList *image_list = NULL;
/* Store the image index to use */
static int current_image;
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
N_("Move the mouse"),
N_("Move the mouse to erase the area and discover the background"),
"Bruno Coudoin ",
NULL,
NULL,
NULL,
NULL,
start_board,
pause_board,
end_board,
is_our_board,
NULL,
NULL,
set_level,
NULL,
NULL,
NULL,
NULL
};
/*
* Main entry point mandatory for each Gcompris's game
* ---------------------------------------------------
*
*/
GET_BPLUGIN_INFO(erase)
/*
* in : boolean TRUE = PAUSE : FALSE = CONTINUE
*
*/
static void pause_board (gboolean pause)
{
if(gcomprisBoard==NULL)
return;
if (timer_id) {
g_source_remove (timer_id);
timer_id = 0;
}
if(gamewon == TRUE && pause == FALSE) /* the game is won */
{
game_won();
}
if (pause == FALSE)
set_cursor();
board_paused = pause;
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
GcomprisProperties *properties = gc_prop_get ();
gcomprisBoard=agcomprisBoard;
gcomprisBoard->level=1;
gcomprisBoard->maxlevel=6;
gcomprisBoard->sublevel=1;
gcomprisBoard->number_of_sublevel=10;
gc_bar_set(GC_BAR_LEVEL);
/* CAUTION: CoverPixmap has MAX_LAYERS elements */
CoverPixmap[0] = gc_rsvg_load("erase/transparent_square.svgz");
CoverPixmap[1] = gc_rsvg_load("erase/transparent_square_green.svgz");
CoverPixmap[2] = gc_rsvg_load("erase/transparent_square_yellow.svgz");
event_handle_id =
g_signal_connect(goo_canvas_get_root_item(gcomprisBoard->canvas),
"button_press_event",
(GCallback) canvas_event, NULL);
if (strcmp(gcomprisBoard->mode,"clic")==0)
board_mode = CLIC;
else if (strcmp(gcomprisBoard->mode,"doubleclic")==0)
board_mode = DOUBLECLIC;
else {
board_mode = NORMAL;
gcomprisBoard->maxlevel=8;
}
init_user_dir();
load_image_from_dir(properties->package_data_dir, &image_list);
load_image_from_dir(properties->user_dir, &image_list);
current_image = 0;
if ( g_slist_length(image_list) == 0)
{
gc_dialog(_("Error: No images found\n"), gc_board_stop);
}
else
{
erase_next_level();
gamewon = FALSE;
pause_board(FALSE);
}
}
}
/* ======================================= */
static void end_board ()
{
int i;
for(i=0; icanvas),
event_handle_id);
pause_board(TRUE);
erase_destroy_all_items();
}
for ( i=0; i < g_slist_length(image_list); i++)
g_free( g_slist_nth_data(image_list, i) );
g_slist_free (image_list);
image_list = NULL;
gcomprisBoard = NULL;
}
/* ======================================= */
static void set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
gcomprisBoard->sublevel=1;
erase_next_level();
}
}
/* ======================================= */
static gboolean is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_ascii_strcasecmp(gcomprisBoard->type, "erase")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
static void set_cursor()
{
GcomprisProperties *properties = gc_prop_get ();
if(properties->defaultcursor == GCOMPRIS_DEFAULT_CURSOR)
{
GdkPixbuf *cursor_pixbuf = gc_pixmap_load("erase/sponge.png");
if(cursor_pixbuf)
{
GdkCursor *cursor = NULL;
cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
cursor_pixbuf,
gdk_pixbuf_get_width(cursor_pixbuf)/2,
gdk_pixbuf_get_height(cursor_pixbuf)/2);
gdk_window_set_cursor(gc_get_window()->window, cursor);
gdk_cursor_unref(cursor);
#if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24
gdk_pixbuf_unref(cursor_pixbuf);
#else
g_object_unref(cursor_pixbuf);
#endif
}
}
}
static int get_num_layers()
{
int layers;
/* Select the number of layer depending on the level */
if(gcomprisBoard->level>6)
layers = 4;
else if(gcomprisBoard->level>4)
layers = 3;
else if(gcomprisBoard->level>2)
layers = 2;
else
layers = 1;
return layers;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void erase_next_level()
{
gc_set_background( goo_canvas_get_root_item(gcomprisBoard->canvas),
g_slist_nth_data (image_list, current_image++) );
if( current_image >= g_slist_length(image_list) )
current_image = 0;
gc_bar_set_level(gcomprisBoard);
erase_destroy_all_items();
gamewon = FALSE;
/* Select level difficulty */
if (board_mode != NORMAL) {
number_of_item_x = 5;
number_of_item_y = 5;
} else {
number_of_item_x = ((gcomprisBoard->level+1)%2+1)*5;
number_of_item_y = ((gcomprisBoard->level+1)%2+1)*5;
assert(!items_per_cell);
items_per_cell = g_new0(int, number_of_item_x * number_of_item_y);
}
/* Try the next level */
erase_create_item();
}
/* ==================================== */
/* Destroy all the items */
static void erase_destroy_all_items()
{
if (normal_delay_id) {
g_source_remove (normal_delay_id);
normal_delay_id = 0;
}
if (timer_id) {
g_source_remove (timer_id);
timer_id = 0;
}
if(boardRootItem!=NULL)
goo_canvas_item_remove(boardRootItem);
boardRootItem = NULL;
number_of_items = 0;
if (items_per_cell) {
g_free(items_per_cell);
items_per_cell = NULL;
}
}
static void add_one_item(int i, int j, int protect)
{
int current_layer = get_num_layers();
double w = (BOARDWIDTH/number_of_item_x) ;
double h = ((BOARDHEIGHT-BARHEIGHT)/number_of_item_y) ;
int item_x = i / w;
int item_y = j / h;
i = item_x * w;
j = item_y * h;
if ((board_mode != NORMAL) && ((item_x+item_y) %2 == 0))
return;
if (current_layer == 4)
current_layer = 1;
while(current_layer--)
{
RsvgDimensionData dimension;
assert(CoverPixmap[current_layer]);
GooCanvasItem *item =
goo_canvas_svg_new (boardRootItem,
CoverPixmap[current_layer],
NULL);
rsvg_handle_get_dimensions(CoverPixmap[current_layer], &dimension);
double scale = h/dimension.height;
goo_canvas_item_set_simple_transform(item,
i,
j,
scale, 0.0);
counter *c = g_new (counter, 1);
c->count = 0 ;
c->max = protect;
protect = 0; /* only protect the top item */
/* if item is not first, it must be keep first time mouse
* pass over in normal mode or in layer 4 */
if (current_layer > 0 || get_num_layers() == 4)
c->max += 1;
g_signal_connect_data (item, "enter_notify_event",
(GCallback) item_event,
(gpointer)c,
(GClosureNotify) g_free, 0);
g_signal_connect (item, "leave_notify_event",
(GCallback) item_event,
(gpointer)c);
g_signal_connect (item, "button_press_event",
(GCallback) item_event,
(gpointer)c);
number_of_items++;
if (items_per_cell)
items_per_cell[item_x * number_of_item_x + item_y]++;
}
}
/* ==================================== */
static GooCanvasItem *erase_create_item()
{
int i,j;
boardRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas),
NULL);
assert(number_of_items == 0);
for(i=0; isublevel++;
if(gcomprisBoard->sublevel>gcomprisBoard->number_of_sublevel) {
/* Try the next level */
gcomprisBoard->sublevel=1;
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
gcomprisBoard->level = gcomprisBoard->maxlevel;
gc_sound_play_ogg ("sounds/bonus.wav", NULL);
}
erase_next_level();
}
static gboolean
erase_one_item (GooCanvasItem *item)
{
gdouble screen_x, screen_y;
int x,y;
SoundPolicy sound_policy = gc_sound_policy_get();
goo_canvas_convert_from_item_space(goo_canvas_item_get_canvas(item),
item, &screen_x, &screen_y);
x = screen_x / (BOARDWIDTH/number_of_item_x);
y = screen_y / ((BOARDHEIGHT-BARHEIGHT)/number_of_item_y);
if (items_per_cell)
items_per_cell[(int) (x * number_of_item_x + y)]--;
goo_canvas_item_remove(item);
if(--number_of_items == 0)
{
gamewon = TRUE;
erase_destroy_all_items();
timer_id = g_timeout_add (4000, (GSourceFunc) bonus, NULL);
}
/* force a cleanup of the sound queue */
if(number_of_items == 0)
gc_sound_policy_set(PLAY_AND_INTERRUPT);
if(number_of_items%2)
gc_sound_play_ogg ("sounds/eraser1.wav", NULL);
else
gc_sound_play_ogg ("sounds/eraser2.wav", NULL);
if(number_of_items == 0)
gc_sound_policy_set(sound_policy);
normal_delay_id = 0;
return FALSE;
}
/* ==================================== */
static gboolean
item_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventCrossing *event,
gpointer data)
{
static GooCanvasItem *previous_clicked_item = NULL;
static guint32 previous_click_time = 0;
counter *c = (counter *) data;
if(board_paused)
return FALSE;
if (board_mode == NORMAL) {
if (event->type == GDK_ENTER_NOTIFY) {
if (c->count < c->max){
c->count++ ;
return FALSE ;
}
/* Are enter & leave always sent in pairs? Don't assume. */
if (normal_delay_id)
g_source_remove (normal_delay_id);
normal_delay_id
= g_timeout_add (50, (GSourceFunc) erase_one_item, target);
} else if (event->type == GDK_LEAVE_NOTIFY) {
if (normal_delay_id)
g_source_remove (normal_delay_id);
normal_delay_id = 0;
}
return FALSE;
}
if (board_mode == CLIC)
if (event->type != GDK_BUTTON_PRESS)
return FALSE;
if (board_mode == DOUBLECLIC)
{
if (event->type != GDK_BUTTON_PRESS)
return FALSE;
else
{
guint32 d = event->time - previous_click_time;
/* Click duration handicap depending on the level */
d += gcomprisBoard->level * 100;
if (previous_clicked_item != item
|| d >= 850)
{
previous_clicked_item = item;
previous_click_time = event->time;
return FALSE;
}
}
}
erase_one_item (target);
return FALSE;
}
static gboolean
canvas_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data)
{
if (!gcomprisBoard || board_paused || gamewon)
return FALSE;
if (board_mode == NORMAL)
{
int x = event->x;
int y = event->y;
int item_x = x / (BOARDWIDTH/number_of_item_x);
int item_y = y / ((BOARDHEIGHT-BARHEIGHT)/number_of_item_y);
if (items_per_cell[item_x * number_of_item_x + item_y] == 0)
add_one_item(x, y, 1);
}
return FALSE;
}
static
void init_user_dir()
{
GcomprisProperties *props = gc_prop_get();
gchar *tmp = g_strconcat(props->user_dir, "/erase", NULL);
if (!g_file_test(tmp, G_FILE_TEST_IS_DIR))
gc_util_create_rootdir(tmp);
g_free(tmp);
tmp = g_strconcat(props->user_dir, "/erase/", _("readme"), ".txt", NULL);
g_file_set_contents(tmp,
_("Put any number of images in this directory.\n"
"They will be used as background in the 'erase' activity.\n"
"The image must be in the 'jpeg' format and be suffixed with"
" '.jpg' or '.jpeg'.\n"
"For best results, they must have a size of 800x520 pixels.\n"),
-1,
NULL);
g_free(tmp);
}
static
void load_image_from_dir(char *base_dir, GSList **image_list)
{
GDir *dir;
const gchar *one_dirent;
gchar *image_dir = g_strconcat(base_dir, "/", "erase", NULL);
dir = g_dir_open(image_dir, 0, NULL);
if (!dir) {
g_warning ("Couldn't open image dir: %s", image_dir);
g_free(image_dir);
return;
}
/* Fill up the music list */
while((one_dirent = g_dir_read_name(dir)) != NULL)
{
if ( (g_str_has_suffix(one_dirent, ".jpg")) ||
(g_str_has_suffix(one_dirent, ".JPG")) ||
(g_str_has_suffix(one_dirent, ".jpeg")) ||
(g_str_has_suffix(one_dirent, ".JPEG")) )
{
gchar *str = g_strdup_printf("%s/%s", image_dir, one_dirent);
*image_list = g_slist_insert (*image_list, str,
RAND(0, g_slist_length(*image_list)));
}
}
g_free(image_dir);
g_dir_close(dir);
}