/* gcompris - clickgame.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 .
*/
#ifdef __APPLE__
# include
#endif
#include
#include "gcompris/gcompris.h"
#include "gcompris/pixbuf_util.h"
#define SOUNDLISTFILE PACKAGE
static gboolean board_paused = TRUE;
static GList *item_list = NULL; // onscreen fish
static GList *item2del_list = NULL;
static GcomprisBoard *gcomprisBoard = NULL;
static gint move_items_id = 0;
static gint animate_id = 0;
static gint drop_items_id = 0;
typedef struct {
double speed;
gint fright;
gint stun;
gint currentItem;
GooCanvasItem *rootitem;
GSList *fwd_frames;
GSList *rev_frames;
GSList *cur_frames; // points to fwd_frames or rev_frames
} FishItem;
static void clickgame_start (GcomprisBoard *agcomprisBoard);
static void clickgame_pause (gboolean pause);
static void clickgame_end (void);
static gboolean clickgame_is_our_board (GcomprisBoard *gcomprisBoard);
static void clickgame_set_level (guint level);
static void clickgame_config(void);
static FishItem *clickgame_create_item();
static gboolean clickgame_drop_items (gpointer data);
static gboolean clickgame_move_items (gpointer data);
static gboolean clickgame_animate_items (gpointer data);
static void clickgame_destroy_item(FishItem *fishitem);
static void clickgame_destroy_items(void);
static void clickgame_destroy_all_items(void);
static void clickgame_next_level(void);
static int gamewon;
static void game_won(void);
static guint32 fallSpeed = 0;
static double moveSpeed = 0.0;
static double imageZoom = 0.0;
static const gchar *smallFish[] =
{
"%s/cichlid1_%d.png",
"%s/cichlid4_%d.png",
"%s/collaris_%d.png",
"%s/discus3_%d.png",
"%s/eel_%d.png",
"%s/teeth_%d.png",
"%s/f02_%d.png",
};
#define NUM_SMALLFISH G_N_ELEMENTS(smallFish)
static const gchar *medFish[] =
{
"%s/f00_%d.png",
"%s/f01_%d.png",
"%s/f03_%d.png",
"%s/f04_%d.png",
"%s/f05_%d.png",
"%s/f06_%d.png",
"%s/f11_%d.png",
"%s/f12_%d.png",
"%s/f13_%d.png",
"%s/QueenAngel_%d.png",
"%s/six_barred_%d.png",
"%s/shark1_%d.png",
};
#define NUM_MEDFISH G_N_ELEMENTS(medFish)
static const gchar *bigFish[] =
{
"%s/blueking2_%d.png",
"%s/butfish_%d.png",
"%s/discus2_%d.png",
"%s/f07_%d.png",
"%s/f08_%d.png",
"%s/f09_%d.png",
"%s/f10_%d.png",
"%s/manta_%d.png",
"%s/newf1_%d.png",
};
#define NUM_BIGFISH G_N_ELEMENTS(bigFish)
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Click On Me",
"Click with the mouse on all swimming fish before they leave the fishtank",
"Bruno Coudoin ",
NULL,
NULL,
NULL,
NULL,
clickgame_start,
clickgame_pause,
clickgame_end,
clickgame_is_our_board,
NULL,
NULL,
clickgame_set_level,
clickgame_config,
NULL,
NULL,
NULL
};
/*
* Main entry point mandatory for each Gcompris's game
* ---------------------------------------------------
*
*/
GET_BPLUGIN_INFO(clickgame)
/*
* in : boolean TRUE = PAUSE : FALSE = CONTINUE
*
*/
static void clickgame_pause (gboolean pause)
{
if(gcomprisBoard==NULL)
return;
if(gamewon == TRUE && pause == FALSE) /* the game is won */
{
game_won();
}
if(pause)
{
if (move_items_id) {
g_source_remove (move_items_id);
move_items_id = 0;
}
if (animate_id) {
g_source_remove (animate_id);
animate_id = 0;
}
if (drop_items_id) {
g_source_remove (drop_items_id);
drop_items_id = 0;
}
}
else
{
if(!drop_items_id) {
drop_items_id = g_timeout_add (200,
clickgame_drop_items, NULL);
}
if(!move_items_id) {
move_items_id = g_timeout_add (200, clickgame_move_items, NULL);
}
if(!animate_id) {
animate_id = g_timeout_add (200, clickgame_animate_items, NULL);
}
}
board_paused = pause;
}
static void
fish_reverse_direction (FishItem *fish)
{
fish->speed = -fish->speed;
g_object_set(g_slist_nth_data(fish->cur_frames,
fish->currentItem),
"visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
fish->cur_frames =
fish->speed<0? fish->rev_frames : fish->fwd_frames;
g_object_set(g_slist_nth_data(fish->cur_frames,
fish->currentItem),
"visibility", GOO_CANVAS_ITEM_VISIBLE, NULL);
}
static void
fish_gobble (FishItem *fishitem)
{
clickgame_destroy_item(fishitem);
gc_sound_play_ogg ("sounds/bubble.wav", NULL);
gcomprisBoard->sublevel++;
gc_score_set(gcomprisBoard->sublevel);
if(gcomprisBoard->sublevel>=gcomprisBoard->number_of_sublevel) {
gamewon = TRUE;
clickgame_destroy_all_items();
gc_bonus_display(gamewon, GC_BONUS_FLOWER);
return;
}
/* Drop a new item now to speed up the game */
if(g_list_length(item_list)==0)
{
/* Remove any pending new item creation to sync the falls */
if (drop_items_id)
g_source_remove (drop_items_id);
drop_items_id =
g_timeout_add (0, clickgame_drop_items, NULL);
}
}
static gboolean
canvas_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data)
{
FishItem *fish;
gdouble mouse_x;
gdouble mouse_y;
int ii;
mouse_x = event->x;
mouse_y = event->y;
if (gcomprisBoard->level >= 3 &&
!(event->state & GDK_SHIFT_MASK)) {
for (ii=0; (fish=g_list_nth_data(item_list, ii)); ii++) {
double dx, dy, near;
int react;
GooCanvasBounds bounds;
goo_canvas_item_get_bounds (fish->rootitem, &bounds);
//printf("fish %d\n", ii);
dy = (mouse_y - (bounds.y1 + (bounds.y2 - bounds.y1) / 2)) / ((bounds.y2 - bounds.y1) / 2);
//printf("dy = %.2f\n", dy);
if (fabs(dy) > 3) continue;
dx = (mouse_x - (bounds.x1 + (bounds.x2 - bounds.x1) / 2)) / ((bounds.x2 - bounds.x1) / 2);
//printf("dx = %.2f\n", dx);
if (fabs(dx) > 3) continue;
// 0 to .9
near = (sqrt(2*3*3) - sqrt(dx*dx + dy*dy))/(1.11 * sqrt(2*3*3));
//printf("near = %.2f\n", near);
react = ((rand() % 1000 < near * 1000) +
(rand() % 1000 < near * 1000));
if (react) {
if (goo_canvas_get_item_at (goo_canvas_item_get_canvas (item),
mouse_x, mouse_y, FALSE) !=
g_slist_nth_data (fish->cur_frames, fish->currentItem) &&
(dx > 0) ^ (fish->speed < 0))
{
fish_reverse_direction (fish);
gc_sound_play_ogg ("sounds/drip.wav", NULL);
}
else
react += 1;
}
if (react >= 2)
fish->fright +=
(1000 + rand() % (int)(near * 2000)) / moveSpeed;
}
}
return TRUE;
}
static gboolean
item_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
FishItem *fishitem)
{
double item_x, item_y;
if(!gcomprisBoard || !event)
return FALSE;
if(board_paused)
return FALSE;
item_x = event->x;
item_y = event->y;
goo_canvas_convert_to_item_space( goo_canvas_item_get_canvas(item),
goo_canvas_item_get_parent(item), &item_x, &item_y);
switch(event->button)
{
case 1:
case 2:
case 3:
{
if (gcomprisBoard->level >= 5 && !fishitem->stun) {
fishitem->stun = 500 * (1 + gcomprisBoard->maxlevel - gcomprisBoard->level) / moveSpeed;
fishitem->fright += 500 / moveSpeed;
} else {
fish_gobble (fishitem);
}
}
break;
case 4:
/* fish up */
goo_canvas_item_translate(item, 0.0, -3.0);
break;
case 5:
/* fish down */
goo_canvas_item_translate(item, 0.0, 3.0);
break;
default:
break;
}
return TRUE;
}
/*
*/
static void clickgame_start (GcomprisBoard *agcomprisBoard)
{
if (!agcomprisBoard)
return;
gcomprisBoard = agcomprisBoard;
/* set initial values for this level */
gcomprisBoard->level = 1;
gcomprisBoard->maxlevel=6;
gcomprisBoard->number_of_sublevel=10;
gc_score_start(SCORESTYLE_NOTE,
BOARDWIDTH - 195,
BOARDHEIGHT - 25,
gcomprisBoard->number_of_sublevel);
gc_bar_set(GC_BAR_LEVEL);
g_signal_connect(goo_canvas_get_root_item(gcomprisBoard->canvas),
"enter_notify_event",
(GCallback) canvas_event, NULL);
clickgame_next_level();
clickgame_pause(FALSE);
}
static void
clickgame_end ()
{
if(gcomprisBoard!=NULL)
{
clickgame_pause(TRUE);
gc_score_end();
clickgame_destroy_all_items();
g_signal_handlers_disconnect_by_func(goo_canvas_get_root_item(gcomprisBoard->canvas),
(GCallback) canvas_event, NULL);
gcomprisBoard->level = 1; // Restart this game to zero
}
gcomprisBoard = NULL;
}
static void
clickgame_set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
clickgame_next_level();
}
}
static gboolean
clickgame_is_our_board (GcomprisBoard *board)
{
if (board)
{
if(g_ascii_strcasecmp(board->type, "clickgame")==0)
{
/* Set the plugin entry */
board->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
static void
clickgame_config ()
{
if(gcomprisBoard!=NULL)
{
clickgame_pause(TRUE);
}
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void clickgame_next_level()
{
static /*const*/ gchar *bglist[] =
{
"clickgame/sea1.jpg",
"clickgame/sea2.jpg",
"clickgame/sea3.jpg",
"clickgame/sea4.jpg",
"clickgame/sea5.jpg",
"clickgame/sea6.jpg"
};
int bgx = gcomprisBoard->level - 1;
if (bgx < 0 || G_N_ELEMENTS(bglist) <= bgx)
bgx = G_N_ELEMENTS(bglist) - 1;
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
bglist[bgx]);
gc_bar_set_level(gcomprisBoard);
/* Try the next level */
moveSpeed=100+(40/(gcomprisBoard->level));
fallSpeed=5000-gcomprisBoard->level*200;
imageZoom =
0.75 + 0.25
* ((double)(gcomprisBoard->maxlevel - gcomprisBoard->level + 1)
/ gcomprisBoard->maxlevel);
gcomprisBoard->sublevel=0;
gc_score_set(gcomprisBoard->sublevel);
while (g_list_length (item_list) < 3) {
FishItem *fish = clickgame_create_item();
if (!fish) break;
goo_canvas_item_translate(fish->rootitem,
fish->speed * (rand() % 200), 0.0);
}
}
static void clickgame_animate_item(FishItem *fishitem)
{
gint currentItem;
/* Manage the animation */
currentItem = fishitem->currentItem;
fishitem->currentItem++;
if(fishitem->currentItem >= g_slist_length(fishitem->cur_frames))
fishitem->currentItem=0;
g_object_set((GooCanvasItem *)g_slist_nth_data(fishitem->cur_frames,
fishitem->currentItem),
"visibility", GOO_CANVAS_ITEM_VISIBLE,
NULL);
g_object_set((GooCanvasItem *)g_slist_nth_data(fishitem->cur_frames,
currentItem),
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
NULL);
}
static void
fish_escape (FishItem *fishitem)
{
item2del_list = g_list_append (item2del_list, fishitem);
gc_sound_play_ogg ("sounds/youcannot.wav", NULL);
if (gcomprisBoard->sublevel) {
--gcomprisBoard->sublevel;
gc_score_set(gcomprisBoard->sublevel);
}
}
static void clickgame_move_item(FishItem *fishitem)
{
double sp = fishitem->speed;
GooCanvasItem *item = fishitem->rootitem;
if (fishitem->stun) {
--fishitem->stun;
sp *= .1 + (rand() % 100) / 1000.0;
} else if (fishitem->fright) {
--fishitem->fright;
sp *= 3 + (rand() % 3000) / 1000.0;
}
goo_canvas_item_translate(item, sp, 0.0);
GooCanvasBounds bounds;
goo_canvas_item_get_bounds (item, &bounds);
if(fishitem->speed>0)
{
if(bounds.x1>BOARDWIDTH)
fish_escape (fishitem);
}
else
{
if(bounds.x2<0)
fish_escape (fishitem);
}
while (g_list_length (item_list) < 3) {
FishItem *fish = clickgame_create_item();
if (!fish) break;
goo_canvas_item_translate(fish->rootitem,
fish->speed * (rand() % 200), 0.0);
}
}
static void clickgame_destroy_item(FishItem *fishitem)
{
GooCanvasItem *item = fishitem->rootitem;
item_list = g_list_remove (item_list, fishitem);
item2del_list = g_list_remove (item2del_list, fishitem);
goo_canvas_item_remove(item);
g_slist_free (fishitem->fwd_frames);
g_slist_free (fishitem->rev_frames);
g_free(fishitem);
}
/* Destroy items that falls out of the canvas */
static void clickgame_destroy_items()
{
FishItem *fishitem;
while(g_list_length(item2del_list)>0)
{
fishitem = g_list_nth_data(item2del_list, 0);
clickgame_destroy_item(fishitem);
}
}
/* Destroy all the items */
static void clickgame_destroy_all_items()
{
while(item_list)
{
FishItem *fishitem = g_list_nth_data(item_list, 0);
clickgame_destroy_item(fishitem);
}
}
/*
* This does the moves of the game items on the play canvas
*
*/
static gboolean clickgame_move_items (gpointer data)
{
g_list_foreach (item_list, (GFunc) clickgame_move_item, NULL);
/* Destroy items that falls out of the canvas */
clickgame_destroy_items();
move_items_id = g_timeout_add (moveSpeed,
clickgame_move_items, NULL);
return(FALSE);
}
/*
* This does the icon animation
*
*/
static gboolean clickgame_animate_items (gpointer data)
{
g_list_foreach (item_list, (GFunc) clickgame_animate_item, NULL);
animate_id = g_timeout_add (1000,
clickgame_animate_items, NULL);
return(FALSE);
}
static GSList *load_random_fish(gboolean smallish)
{
int fish;
const gchar **extraFish;
const gchar **surpriseFish;
int num_extra;
int num_surprise;
if (smallish) {
extraFish = smallFish;
num_extra = NUM_SMALLFISH;
surpriseFish = bigFish;
num_surprise = NUM_BIGFISH;
} else {
extraFish = bigFish;
num_extra = NUM_BIGFISH;
surpriseFish = smallFish;
num_surprise = NUM_SMALLFISH;
}
fish = rand() % (NUM_MEDFISH + num_extra + 2);
const gchar *path;
if (fish < NUM_MEDFISH) {
path = medFish[fish];
} else if (fish < NUM_MEDFISH + num_extra) {
path = extraFish[fish - NUM_MEDFISH];
} else {
fish = rand() % num_surprise;
path = surpriseFish[fish];
}
int frame = 0;
GSList *ilist = 0;
while(1)
{
if (frame) {
gchar *exists = gc_file_find_absolute (path, "clickgame", frame);
g_free (exists);
if (!exists) break;
}
GdkPixbuf *pixmap = gc_pixmap_load (path, "clickgame", frame);
if (!pixmap) break;
ilist = g_slist_prepend (ilist, pixmap);
++frame;
}
return g_slist_reverse (ilist);
}
static FishItem *
clickgame_create_item()
{
GooCanvasItem *parent = \
goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas),
NULL);
GdkPixbuf *pixmap = NULL;
GdkPixbuf *pixmap2 = NULL;
GooCanvasItem *rootitem;
FishItem *fishitem;
double x, y;
gint i, length;
GSList *ilist;
/* Avoid to have too much items displayed */
if(g_list_length(item_list)>5)
return NULL;
// select smallish fish on even levels, also see imageZoom
ilist = load_random_fish (!(gcomprisBoard->level & 1));
fishitem = g_malloc(sizeof(FishItem));
fishitem->currentItem = 0;
fishitem->speed = (double)(g_random_int()%(60))/10 - 3;
fishitem->fright = 0;
fishitem->stun = 0;
fishitem->fwd_frames = NULL;
fishitem->rev_frames = NULL;
pixmap = (GdkPixbuf *)g_slist_nth_data(ilist, 0);
if(pixmap==NULL)
return NULL;
if(fishitem->speed<0)
{
x = (double) BOARDWIDTH;
fishitem->speed=MIN(fishitem->speed, -1);
}
else
{
x = (double) -gdk_pixbuf_get_width(pixmap)*imageZoom;
fishitem->speed = MAX(fishitem->speed, 1);
}
rootitem = \
goo_canvas_group_new (parent,
NULL);
g_signal_connect(rootitem, "button_press_event",
(GCallback) item_event, fishitem);
fishitem->rootitem = rootitem;
length = g_slist_length(ilist);
y = (g_random_int()%(BOARDHEIGHT - BARHEIGHT -
(guint)(gdk_pixbuf_get_height(pixmap)*
imageZoom)));
for(i=0; ifwd_frames = g_slist_prepend (fishitem->fwd_frames, fwd);
fishitem->rev_frames = g_slist_prepend (fishitem->rev_frames, rev);
g_object_set (fwd,
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
NULL);
g_object_set (rev,
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
NULL);
}
g_slist_free (ilist);
fishitem->fwd_frames = g_slist_reverse (fishitem->fwd_frames);
fishitem->rev_frames = g_slist_reverse (fishitem->rev_frames);
fishitem->cur_frames =
fishitem->speed<0? fishitem->rev_frames : fishitem->fwd_frames;
g_object_set(g_slist_nth_data(fishitem->cur_frames,
fishitem->currentItem),
"visibility", GOO_CANVAS_ITEM_VISIBLE,
NULL);
item_list = g_list_append (item_list, fishitem);
return (fishitem);
}
/*
* This is called on a low frequency and is used to drop new items
*
*/
static gboolean clickgame_drop_items (gpointer data)
{
clickgame_create_item();
drop_items_id = g_timeout_add (fallSpeed,
clickgame_drop_items, NULL);
return (FALSE);
}
/* ==================================== */
static void game_won()
{
gcomprisBoard->sublevel++;
if(gcomprisBoard->sublevel>=gcomprisBoard->number_of_sublevel) {
/* Try the next level */
gcomprisBoard->sublevel=0;
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
gcomprisBoard->level = gcomprisBoard->maxlevel;
gc_sound_play_ogg ("sounds/bonus.wav", NULL);
}
clickgame_next_level();
}