/* gcompris - canal_lock.c
*
* Copyright (C) 2001, 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 "gcompris/gcompris.h"
#define SOUNDLISTFILE PACKAGE
#define ANIMATE_SPEED 30
#define CANAL_COLOR 0x0000B8FF
#define LOCK_COLOR 0x8cc679FF
#define LOCK_COLOR_H 0x71A65FFF
#define CANALLOCK_COLOR 0xd1cd0cFF
#define CANALLOCK_COLOR_H 0xf1ed1cFF
#define BASE_LINE 396
#define LEFT_CANAL_HEIGHT 90
#define LEFT_CANAL_WIDTH 329
#define RIGHT_CANAL_HEIGHT 191
#define RIGHT_CANAL_WIDTH 325
#define MIDDLE_CANAL_WIDTH (BOARDWIDTH - RIGHT_CANAL_WIDTH - LEFT_CANAL_WIDTH)
#define LOCK_WIDTH 20
#define LOCK_HEIGHT_MAX (RIGHT_CANAL_HEIGHT + 40)
#define LOCK_HEIGHT_MIN 60
#define LOCK_RHEIGHT_MIN 160
#define SUBCANAL_BASE_LINE (BASE_LINE + 84)
#define SUBCANAL_HEIGHT 40
#define CANALLOCK_WIDTH 30
#define CANALLOCK_HEIGHT_MAX SUBCANAL_HEIGHT
#define CANALLOCK_HEIGHT_MIN 15
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 set_level (guint level);
static int gamewon;
static int from;
static gint timer_id;
static gboolean animation;
static GooCanvasItem *boardRootItem = NULL;
static GooCanvasItem *lock_left_item = NULL;
static GooCanvasItem *lock_right_item = NULL;
static GooCanvasItem *canallock_left_item = NULL;
static GooCanvasItem *canallock_right_item = NULL;
static GooCanvasItem *canal_middle_item = NULL;
static GooCanvasItem *tuxboat_item = NULL;
static double tuxboat_width;
static GooCanvasItem *left_red_on_item;
static GooCanvasItem *left_green_on_item;
static GooCanvasItem *right_red_on_item;
static GooCanvasItem *right_green_on_item;
#define BOAT_POS_LEFT 1
#define BOAT_POS_MIDDLE 2
#define BOAT_POS_RIGHT 3
static guint boat_position;
static gboolean right_way;
static gboolean lock_left_up;
static gboolean lock_right_up;
static gboolean lock_water_low;
static gboolean canallock_left_up;
static gboolean canallock_right_up;
static double timer_item_limit_y, timer_item_limit_x;
static GooCanvasItem *timer_item;
static gint timer_step_y1, timer_step_x1;
static GooCanvasItem *canal_lock_create_item(GooCanvasItem *parent);
static void canal_lock_destroy_all_items(void);
static void canal_lock_next_level(void);
static gboolean item_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data);
static gboolean hightlight(GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer status);
static gboolean animate_step(gpointer data);
static void update_water();
static void toggle_lock(GooCanvasItem *item);
static void update_lights();
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Operate a canal lock",
"Tux is in trouble in his ship. He needs to take it through a lock",
"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(canal_lock)
/*
* in : boolean TRUE = PAUSE : FALSE = CONTINUE
*
*/
static void pause_board (gboolean pause)
{
if(gcomprisBoard==NULL)
return;
board_paused = pause;
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
gcomprisBoard->level=1;
gcomprisBoard->maxlevel=2;
gcomprisBoard->sublevel=1;
gcomprisBoard->number_of_sublevel=1; /* Go to next level after this number of 'play' */
boardRootItem =
goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas),
NULL);
/* Try the next level */
canal_lock_create_item(boardRootItem);
canal_lock_next_level();
gc_bar_set(0);
gc_bar_location(5, -1, -1);
animation = FALSE;
pause_board(FALSE);
}
}
/* ======================================= */
static void end_board ()
{
// If we don't stop animation, there may be a segfault if leaving while the animation is running
if (timer_id) {
g_source_remove (timer_id);
timer_id = 0;
}
animation = FALSE;
if(gcomprisBoard!=NULL)
{
pause_board(TRUE);
canal_lock_destroy_all_items();
}
gcomprisBoard = NULL;
}
/* ======================================= */
static void set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
gcomprisBoard->sublevel=1;
canal_lock_next_level();
}
}
/* ======================================= */
static gboolean is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_ascii_strcasecmp(gcomprisBoard->type, "canal_lock")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void canal_lock_next_level()
{
gc_bar_set_level(gcomprisBoard);
gamewon = FALSE;
from = 0;
/* Original state of the lock */
boat_position = BOAT_POS_LEFT;
right_way = TRUE;
lock_left_up = TRUE;
lock_right_up = TRUE;
lock_water_low = TRUE;
canallock_left_up = TRUE;
canallock_right_up = TRUE;
update_lights();
}
/* ==================================== */
/* Destroy all the items */
static void canal_lock_destroy_all_items()
{
if(boardRootItem!=NULL)
goo_canvas_item_remove(boardRootItem);
boardRootItem = NULL;
}
static void
set_lock_event(GooCanvasItem *item)
{
g_signal_connect(item, "button-press-event",
(GCallback) item_event,
NULL);
g_signal_connect(item, "enter_notify_event",
(GCallback) hightlight,
GINT_TO_POINTER(TRUE));
g_signal_connect(item, "leave_notify_event",
(GCallback) hightlight,
GINT_TO_POINTER(FALSE));
}
/* ==================================== */
static GooCanvasItem *canal_lock_create_item(GooCanvasItem *boardRootItem)
{
RsvgHandle *svg_handle;
svg_handle = gc_rsvg_load("canal_lock/canal_lock.svgz");
/* The background */
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#BACKGROUND",
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
/* The boat */
tuxboat_item = goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#BOAT_NO_SAIL", NULL);
g_signal_connect(tuxboat_item, "button-press-event",
(GCallback) item_event,
NULL);
gc_item_focus_init(tuxboat_item, NULL);
GooCanvasBounds bounds;
goo_canvas_item_get_bounds(tuxboat_item, &bounds);
tuxboat_width = bounds.x2 - bounds.x1 + 20;
/* The left lights */
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#LEFT_RED_OFF",
"visibility", GOO_CANVAS_ITEM_VISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#LEFT_GREEN_OFF",
"visibility", GOO_CANVAS_ITEM_VISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
left_red_on_item =
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#LEFT_RED_ON",
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
left_green_on_item =
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#LEFT_GREEN_ON",
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#LEFT_LIGHT_BASE",
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
/* The right lights */
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#RIGHT_RED_OFF",
"visibility", GOO_CANVAS_ITEM_VISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#RIGHT_GREEN_OFF",
"visibility", GOO_CANVAS_ITEM_VISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
right_red_on_item = goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#RIGHT_RED_ON",
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
right_green_on_item = goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#RIGHT_GREEN_ON",
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
goo_canvas_svg_new (boardRootItem, svg_handle,
"svg-id", "#RIGHT_LIGHT_BASE",
"pointer-events", GOO_CANVAS_EVENTS_NONE,
NULL);
/* This is the middle canal */
canal_middle_item = goo_canvas_rect_new (boardRootItem,
LEFT_CANAL_WIDTH,
BASE_LINE - LEFT_CANAL_HEIGHT,
MIDDLE_CANAL_WIDTH,
LEFT_CANAL_HEIGHT,
"fill_color_rgba", CANAL_COLOR,
"line-width", (double) 0,
NULL);
goo_canvas_item_raise(tuxboat_item, canal_middle_item);
/* This is the left lock */
lock_left_item = goo_canvas_rect_new (boardRootItem,
LEFT_CANAL_WIDTH - LOCK_WIDTH / 2,
BASE_LINE - LOCK_HEIGHT_MAX,
LOCK_WIDTH,
LOCK_HEIGHT_MAX,
"fill_color_rgba", LOCK_COLOR,
"line-width", (double) 0,
NULL);
set_lock_event(lock_left_item);
/* This is the right lock */
lock_right_item = goo_canvas_rect_new (boardRootItem,
LEFT_CANAL_WIDTH + MIDDLE_CANAL_WIDTH - LOCK_WIDTH / 2,
BASE_LINE - LOCK_HEIGHT_MAX,
LOCK_WIDTH,
LOCK_HEIGHT_MAX,
"fill_color_rgba", LOCK_COLOR,
"line-width", (double) 0,
NULL);
set_lock_event(lock_right_item);
/* And to finish, the 2 canal locks */
canallock_left_item =
goo_canvas_rect_new (boardRootItem,
LEFT_CANAL_WIDTH + MIDDLE_CANAL_WIDTH * 0.1,
SUBCANAL_BASE_LINE - SUBCANAL_HEIGHT,
LOCK_WIDTH / 2,
SUBCANAL_HEIGHT,
"fill_color_rgba", CANALLOCK_COLOR,
"line-width", (double) 0,
NULL);
set_lock_event(canallock_left_item);
canallock_right_item =
goo_canvas_rect_new (boardRootItem,
LEFT_CANAL_WIDTH + MIDDLE_CANAL_WIDTH * 0.9,
SUBCANAL_BASE_LINE - SUBCANAL_HEIGHT,
LOCK_WIDTH / 2,
SUBCANAL_HEIGHT,
"fill_color_rgba", CANALLOCK_COLOR,
"line-width", (double) 0,
NULL);
set_lock_event(canallock_right_item);
g_object_unref (svg_handle);
return NULL;
}
/* ==================================== */
/* Move the boat to the next possible position */
static void
move_boat()
{
/* If there is already an animation do nothing
* else set animation to avoid deadlock
*/
if(animation)
return;
animation = TRUE;
if(boat_position == BOAT_POS_LEFT && !lock_left_up)
{
boat_position = BOAT_POS_MIDDLE;
timer_item_limit_x = LEFT_CANAL_WIDTH + (MIDDLE_CANAL_WIDTH - tuxboat_width)/2;
timer_step_x1 = 2;
g_object_set (tuxboat_item,
"svg-id", "#BOAT", NULL);
}
else if(boat_position == BOAT_POS_MIDDLE && !lock_left_up)
{
boat_position = BOAT_POS_LEFT;
timer_item_limit_x = (LEFT_CANAL_WIDTH - tuxboat_width)/2;
timer_step_x1 = -2;
if (from == 1)
{
gamewon = TRUE;
from = 0;
}
g_object_set (tuxboat_item,
"svg-id", "#BOAT", NULL);
}
else if(boat_position == BOAT_POS_MIDDLE && !lock_right_up)
{
boat_position = BOAT_POS_RIGHT;
timer_item_limit_x = LEFT_CANAL_WIDTH + MIDDLE_CANAL_WIDTH + (RIGHT_CANAL_WIDTH - tuxboat_width)/2;
timer_step_x1 = 2;
if (from == 0)
{
gamewon = TRUE;
from = 1;
}
g_object_set (tuxboat_item,
"svg-id", "#BOAT", NULL);
}
else if(boat_position == BOAT_POS_RIGHT && !lock_right_up)
{
boat_position = BOAT_POS_MIDDLE;
timer_item_limit_x = LEFT_CANAL_WIDTH + (MIDDLE_CANAL_WIDTH - tuxboat_width)/2;
timer_step_x1 = -2;
g_object_set (tuxboat_item,
"svg-id", "#BOAT", NULL);
}
else
{
/* No possible move */
gc_sound_play_ogg ("sounds/crash.ogg", NULL);
animation = FALSE;
return;
}
gc_item_focus_remove(tuxboat_item, NULL);
gc_sound_play_ogg ("sounds/eraser2.wav", NULL);
timer_item = tuxboat_item;
timer_step_y1 = 0;
timer_id = g_timeout_add (ANIMATE_SPEED, animate_step, NULL);
}
/* ==================================== */
/* Update the water level if necessary */
static void update_water()
{
gboolean status = TRUE;
double y1 = 0;
gint min = BASE_LINE - LEFT_CANAL_HEIGHT;
/* If there is already an animation do nothing else set
animation to avoid deadlock */
if(animation)
return;
animation = TRUE;
if((!canallock_left_up && !lock_water_low) ||
(!canallock_right_up && lock_water_low))
{
status = !lock_water_low;
lock_water_low = !lock_water_low;
y1 = BASE_LINE - RIGHT_CANAL_HEIGHT;
}
else
{
/* The water level is correct */
animation = FALSE;
return;
}
timer_item = canal_middle_item;
timer_item_limit_y = (status ? min :
y1);
timer_step_y1 = (status ? 2 : -2);
timer_step_x1 = 0;
gc_item_focus_remove(tuxboat_item, NULL);
timer_id = g_timeout_add (ANIMATE_SPEED, animate_step, NULL);
}
/* ==================================== */
/* Toggle the given lock */
static void
toggle_lock(GooCanvasItem *item)
{
gboolean status = TRUE;
double y1 = 0;
gint min = 0;
guint animate_speed = 0;
/* If there is already an animation do nothing else set animation to avoid deadlock */
if(animation)
return;
animation = TRUE;
gc_sound_play_ogg ("sounds/bleep.wav", NULL);
if(item == lock_left_item)
{
status = lock_left_up;
lock_left_up = !lock_left_up;
y1 = BASE_LINE - LOCK_HEIGHT_MAX;
min = BASE_LINE - LOCK_HEIGHT_MIN;
animate_speed = ANIMATE_SPEED;
}
else if(item == lock_right_item)
{
status = lock_right_up;
lock_right_up = !lock_right_up;
y1 = BASE_LINE - LOCK_HEIGHT_MAX;
min = BASE_LINE - LOCK_RHEIGHT_MIN;
animate_speed = ANIMATE_SPEED;
}
else if(item == canallock_left_item)
{
status = canallock_left_up;
canallock_left_up = !canallock_left_up;
y1 = SUBCANAL_BASE_LINE - SUBCANAL_HEIGHT;
min = SUBCANAL_BASE_LINE - CANALLOCK_HEIGHT_MIN;
animate_speed = ANIMATE_SPEED;
}
else if(item == canallock_right_item)
{
status = canallock_right_up;
canallock_right_up = !canallock_right_up;
y1 = SUBCANAL_BASE_LINE - SUBCANAL_HEIGHT;
min = SUBCANAL_BASE_LINE - CANALLOCK_HEIGHT_MIN;
animate_speed = ANIMATE_SPEED;
}
timer_item = item;
timer_item_limit_y = (status ? min :
y1);
timer_step_y1 = (status ? 2 : -2);
timer_step_x1 = 0;
gc_item_focus_remove(tuxboat_item, NULL);
timer_id = g_timeout_add (animate_speed, animate_step,
NULL);
}
/* ==================================== */
static gboolean
animate_step(gpointer data)
{
if(!gcomprisBoard)
return FALSE;
GooCanvasBounds bounds;
goo_canvas_item_get_bounds(timer_item, &bounds);
if(GOO_IS_CANVAS_SVG(timer_item))
goo_canvas_item_translate(timer_item, timer_step_x1, timer_step_y1);
else if(GOO_IS_CANVAS_RECT(timer_item))
g_object_set(timer_item,
"x", bounds.x1 + timer_step_x1,
"y", bounds.y1 + timer_step_y1,
"height", bounds.y2 - bounds.y1 - timer_step_y1,
NULL);
/* Special case for raising/lowering the boat */
if(boat_position==BOAT_POS_MIDDLE && timer_item==canal_middle_item)
{
goo_canvas_item_translate(tuxboat_item, 0, timer_step_y1);
gc_item_focus_remove(tuxboat_item, NULL);
}
if((bounds.y1 >= timer_item_limit_y && timer_step_y1 > 0) ||
(bounds.y1 <= timer_item_limit_y && timer_step_y1 < 0))
{
g_source_remove (timer_id);
timer_id = 0;
animation = FALSE;
update_water();
gc_item_focus_init(tuxboat_item, NULL);
g_object_set (tuxboat_item,
"svg-id", "#BOAT_NO_SAIL", NULL);
}
else if((bounds.x1 >= timer_item_limit_x && timer_step_x1 > 0) ||
(bounds.x1 <= timer_item_limit_x && timer_step_x1 < 0))
{
g_source_remove (timer_id);
timer_id = 0;
animation = FALSE;
update_water();
if (gamewon)
{
gc_bonus_display(TRUE, GC_BONUS_FLOWER);
gamewon = FALSE;
}
gc_item_focus_init(tuxboat_item, NULL);
g_object_set (tuxboat_item,
"svg-id", "#BOAT_NO_SAIL", NULL);
}
return TRUE;
}
/* ==================================== */
/* Highlight the given item */
static gboolean
hightlight(GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer status_)
{
guint color = 0;
gboolean status = GPOINTER_TO_INT(status_);
/* This is an image, not a rectangle */
if(item == tuxboat_item)
return FALSE;
if(item == lock_left_item ||
item == lock_right_item)
{
color = (status ? LOCK_COLOR_H : LOCK_COLOR);
}
else if (item == canallock_left_item ||
item == canallock_right_item)
{
color = (status ? CANALLOCK_COLOR_H : CANALLOCK_COLOR);
}
g_object_set(item,
"fill_color_rgba", color,
NULL);
return TRUE;
}
/* ==================================== */
static gboolean
item_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data)
{
if(board_paused)
return FALSE;
/* If there is already an animation do nothing */
if(animation)
return FALSE;
if(item == lock_left_item)
{
if(lock_water_low && canallock_right_up)
toggle_lock(item);
else
gc_sound_play_ogg ("sounds/crash.ogg", NULL);
}
else if(item == lock_right_item)
{
if(!lock_water_low && canallock_left_up)
toggle_lock(item);
else
gc_sound_play_ogg ("sounds/crash.ogg", NULL);
}
else if(item == canallock_left_item && canallock_right_up)
{
if(lock_right_up)
toggle_lock(item);
else
gc_sound_play_ogg ("sounds/crash.ogg", NULL);
}
else if(item == canallock_right_item && canallock_left_up)
{
if(lock_left_up)
toggle_lock(item);
else
gc_sound_play_ogg ("sounds/crash.ogg", NULL);
}
else if(item == tuxboat_item)
{
move_boat();
}
else
{
gc_sound_play_ogg ("sounds/crash.ogg", NULL);
}
update_lights();
return FALSE;
}
static void update_lights()
{
if(lock_water_low && !lock_left_up)
{
g_object_set (left_red_on_item,
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
NULL);
g_object_set (left_green_on_item,
"visibility", GOO_CANVAS_ITEM_VISIBLE,
NULL);
}
else
{
g_object_set (left_red_on_item,
"visibility", GOO_CANVAS_ITEM_VISIBLE,
NULL);
g_object_set (left_green_on_item,
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
NULL);
}
if(!lock_water_low && !lock_right_up)
{
g_object_set (right_red_on_item,
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
NULL);
g_object_set (right_green_on_item,
"visibility", GOO_CANVAS_ITEM_VISIBLE,
NULL);
}
else
{
g_object_set (right_red_on_item,
"visibility", GOO_CANVAS_ITEM_VISIBLE,
NULL);
g_object_set (right_green_on_item,
"visibility", GOO_CANVAS_ITEM_INVISIBLE,
NULL);
}
}