/* gcompris - crane.c
*
* Copyright (C) 2005 Marc BRUN
*
* 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
#define SOUNDLISTFILE PACKAGE
#define CRANE_BUTTON_SPACE 60 // Space between a pair (left/right or up/down)
#define CRANE_BUTTON_LEFT_X 85
#define CRANE_BUTTON_LEFT_Y 447
#define CRANE_BUTTON_RIGHT_X (CRANE_BUTTON_LEFT_X + CRANE_BUTTON_SPACE)
#define CRANE_BUTTON_RIGHT_Y CRANE_BUTTON_LEFT_Y + 2
#define CRANE_BUTTON_UP_X 220
#define CRANE_BUTTON_UP_Y CRANE_BUTTON_LEFT_Y - 5
#define CRANE_BUTTON_DOWN_X CRANE_BUTTON_UP_X + CRANE_BUTTON_SPACE
#define CRANE_BUTTON_DOWN_Y CRANE_BUTTON_UP_Y
#define CRANE_FRAME_X 38
#define CRANE_FRAME_Y 137
#define CRANE_ROPE_Y CRANE_FRAME_Y - 32
#define CRANE_FRAME_COLUMN 6
#define CRANE_FRAME_LINE 5
#define CRANE_FRAME_CELL 52
#define CRANE_FRAME_BORDER 6
#define CRANE_FRAME_IMAGE_SIZE 40
#define CRANE_FRAME_MODEL_X 460
#define CRANE_FRAME_MODEL_Y 50
#define DOWN 0 /* Warning ordering is important */
#define UP 1
#define LEFT 2
#define RIGHT 3
#define MAX_LEVEL 6 /* Don't raise this number except if putting more values
* in pixmap[] array, in place_item function
*/
// List of images to use in the game
static gchar *imageList[] =
{
"crane/water_spot1.png",
"crane/water_spot2.png",
"crane/water_drop1.png",
"crane/water_drop2.png",
"crane/tux.png",
"crane/triangle1.png",
"crane/triangle2.png",
"crane/rectangle1.png",
"crane/rectangle2.png",
"crane/square1.png",
"crane/square2.png",
"crane/bulb.png",
"crane/letter-a.png",
"crane/letter-b.png"
};
#define NB_ELEMENT G_N_ELEMENTS(imageList)
// Types
// Structure describes the objects of the frames
typedef struct {
GdkPixbuf *pixmap;
double x;
double y;
double h;
double w;
} crane_object;
// Structure decribes a movement
typedef struct {
int x; // x movement
int y; // y movement
int nb; // how much of x/y movement (to give a smooth effect) ?
} move_object;
// Global variables
static GcomprisBoard *gcomprisBoard = NULL;
static gboolean board_paused = TRUE;
static GnomeCanvasGroup *boardRootItem = NULL;
static GnomeCanvasItem *selected_item = NULL;
static GnomeCanvasItem *red_hands = NULL;
static GnomeCanvasItem *crane_rope_item = NULL;
static gint timer_id = 0;
static gint nb_move = 0;
static gboolean moving = FALSE;
static move_object my_move;
static int list_answer[CRANE_FRAME_LINE * CRANE_FRAME_COLUMN];
static int list_game[CRANE_FRAME_LINE * CRANE_FRAME_COLUMN];
static GnomeCanvasPoints *crane_rope;
// gcompris functions
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 GnomeCanvasItem *crane_create_item();
static void crane_destroy_all_items(void);
static void crane_next_level(void);
// crane functions
static gint item_event(GnomeCanvasItem *item, GdkEvent *event, gpointer data);
static gint arrow_event(GnomeCanvasItem *item, GdkEvent *event, gpointer data);
static guint smooth_move(move_object *);
static int is_allowed_move (double, double, int);
static void shuffle_list(int list[], int);
static void select_item(GnomeCanvasItem *);
static int get_item_index (double, double);
static void draw_arrow(void);
static void draw_frame(int, int);
static void draw_redhands(void);
static void place_item(int, int, int);
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Duplicate the top right model.",
"Select item in the bottom left frame and move them with the crane's arrows.",
"Marc BRUN",
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(crane)
static void pause_board (gboolean pause)
{
if (gcomprisBoard == NULL)
return;
if (timer_id) {
gtk_timeout_remove (timer_id);
timer_id = 0;
}
/* the game is won */
if (gamewon == TRUE && pause == FALSE)
game_won();
board_paused = pause;
}
static void start_board (GcomprisBoard *agcomprisBoard)
{
if (agcomprisBoard != NULL) {
gchar *img;
gcomprisBoard = agcomprisBoard;
gcomprisBoard->level = 1;
gcomprisBoard->maxlevel = MAX_LEVEL;
gcomprisBoard->sublevel = 1;
gcomprisBoard->number_of_sublevel = 1; /* Go to next level after this number of 'play' */
gc_bar_set(GC_BAR_LEVEL);
img = gc_skin_image_get("gcompris-bg.jpg");
gc_set_background(gnome_canvas_root(gcomprisBoard->canvas),
img);
g_free(img);
crane_next_level();
gamewon = FALSE;
pause_board(FALSE);
}
}
static void end_board () {
if (timer_id) {
gtk_timeout_remove (timer_id);
timer_id = 0;
}
if (gcomprisBoard != NULL) {
pause_board(TRUE);
crane_destroy_all_items();
}
gcomprisBoard = NULL;
}
/* ======================================= */
static void set_level (guint level) {
if (gcomprisBoard != NULL) {
gcomprisBoard->level = level;
gcomprisBoard->sublevel = 1;
crane_next_level();
}
}
/* ======================================= */
static gboolean is_our_board (GcomprisBoard *gcomprisBoard) {
if (gcomprisBoard) {
if (g_strcasecmp(gcomprisBoard->type, "crane") == 0) {
/* Set the plugin entry */
gcomprisBoard->plugin = &menu_bp;
return TRUE;
}
}
return FALSE;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void crane_next_level() {
gc_bar_set_level(gcomprisBoard);
crane_destroy_all_items();
gamewon = FALSE;
/* Try the next level */
crane_create_item();
}
/* ==================================== */
/* Destroy all the items */
static void crane_destroy_all_items()
{
if (timer_id) {
gtk_timeout_remove (timer_id);
timer_id = 0;
}
gnome_canvas_points_free(crane_rope);
if(boardRootItem != NULL)
gtk_object_destroy (GTK_OBJECT(boardRootItem));
boardRootItem = NULL;
}
/* ==================================== */
static GnomeCanvasItem *crane_create_item()
{
int i;
int nb_element;
GdkPixbuf *pixmap;
boardRootItem = GNOME_CANVAS_GROUP(gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
gnome_canvas_group_get_type (),
"x", (double) 0,
"y", (double) 0,
NULL));
pixmap = gc_pixmap_load("crane/crane-bg.png");
gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap,
"x", 0.0,
"y", 0.0,
"anchor", GTK_ANCHOR_NW,
NULL);
gdk_pixbuf_unref(pixmap);
// The four arrows on the crane
draw_arrow();
// The leading frames on both model (answer) and boardgame, only for first levels
if (gcomprisBoard->level < 5) {
draw_frame(CRANE_FRAME_X, CRANE_FRAME_Y); // Boardgame
draw_frame(CRANE_FRAME_MODEL_X, CRANE_FRAME_MODEL_Y); // Model
}
// The red corners (hands) that show the selected item
draw_redhands();
nb_element = gcomprisBoard->level * 2 + 2;
// Answer frame => the unmovable items of the model
for (i = 0 ; i < nb_element ; i++) list_answer[i] = i;
for ( ; i < CRANE_FRAME_LINE * CRANE_FRAME_COLUMN ; i ++) list_answer[i] = -1;
shuffle_list(list_answer, CRANE_FRAME_LINE * CRANE_FRAME_COLUMN);
place_item(CRANE_FRAME_MODEL_X, CRANE_FRAME_MODEL_Y, 0);
// Game frame => the playable items
for (i = 0 ; i < nb_element ; i++) list_game[i] = i;
for ( ; i < CRANE_FRAME_LINE * CRANE_FRAME_COLUMN ; i ++) list_game[i] = -1;
shuffle_list(list_game, CRANE_FRAME_LINE * CRANE_FRAME_COLUMN);
place_item(CRANE_FRAME_X, CRANE_FRAME_Y, 1);
return NULL;
}
// Display an happy face for end of level
static void bonus() {
gc_bonus_display(gamewon, GC_BONUS_SMILEY);
gc_sound_play_ogg ("sounds/bonus.wav", NULL);
timer_id = 0;
}
// Display a 'end of game' animation
static void finished() {
gc_bonus_end_display(GC_BOARD_FINISHED_RANDOM);
timer_id = 0;
}
// One more level completed
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) { // all levels completed : the current board is finished
timer_id = g_timeout_add (2000, (GtkFunction) finished, NULL);
return;
}
}
crane_next_level();
}
// Event on item
static gint item_event(GnomeCanvasItem *item, GdkEvent *event, gpointer data) {
if(board_paused)
return FALSE;
if (event->type == GDK_MOTION_NOTIFY) // Mouse moved
return FALSE;
// Select item if left click on it
if (event->type == GDK_BUTTON_PRESS && event->button.button == 1)
{
gc_sound_play_ogg ("sounds/bleep.wav", NULL);
select_item(item);
}
return FALSE;
}
// Event on arrow
static gint arrow_event(GnomeCanvasItem *item, GdkEvent *event, gpointer data) {
int success;
double x,y;
double dx1, dy1, dx2, dy2;
int direction = GPOINTER_TO_INT(data);
int i;
int index, new_index;
if (board_paused)
return FALSE;
if (moving) // An object is already moving
return FALSE;
if (gamewon)
return FALSE;
if (event->type == GDK_MOTION_NOTIFY) // Mouse moved
return FALSE;
if (selected_item == NULL)
return FALSE;
// Left click on an arrow move the selected item
if (event->type == GDK_BUTTON_PRESS && event->button.button == 1) {
gc_sound_play_ogg ("sounds/scroll.wav", NULL);
x = event->button.x;
y = event->button.y;
gnome_canvas_item_get_bounds(selected_item, &dx1, &dy1, &dx2, &dy2);
switch (direction) {
case LEFT:
my_move.x = -1;
my_move.y = 0;
break;
case RIGHT:
my_move.x = 1;
my_move.y = 0;
break;
case UP:
my_move.x = 0;
my_move.y = -1;
break;
case DOWN:
my_move.x = 0;
my_move.y = 1;
break;
}
// Check if the move doesn't go out of the frame
if (is_allowed_move(dx1, dy1, direction)) {
index = get_item_index(dx1, dy1);
new_index = index + my_move.x + (my_move.y * CRANE_FRAME_COLUMN);
// Check if no object is already here
if (list_game[new_index] == -1) {
// Do a smooth move
my_move.nb = 52;
timer_id = g_timeout_add(10, (GtkFunction) smooth_move, &my_move);
list_game[new_index] = list_game[index];
list_game[index] = -1;
}
}
}
// Check if the level is won
success = 1;
for (i = 0 ; i < CRANE_FRAME_LINE * CRANE_FRAME_COLUMN ; i++) {
if (list_answer[i] != list_game[i]) success = 0;
}
if (success) {
gamewon = TRUE;
timer_id = g_timeout_add (1200, (GtkFunction) bonus, NULL);
}
return FALSE;
}
// Draw the four arrows on the crane
static void draw_arrow() {
GnomeCanvasItem *item_arrow = NULL;
int i;
crane_object arrow[4];
arrow[0].pixmap = gc_pixmap_load("crane/arrow_down.png");
arrow[0].x = CRANE_BUTTON_DOWN_X;
arrow[0].y = CRANE_BUTTON_DOWN_Y;
arrow[1].pixmap = gc_pixmap_load("crane/arrow_up.png");
arrow[1].x = CRANE_BUTTON_UP_X;
arrow[1].y = CRANE_BUTTON_UP_Y;
arrow[2].pixmap = gc_pixmap_load("crane/arrow_left.png");
arrow[2].x = CRANE_BUTTON_LEFT_X;
arrow[2].y = CRANE_BUTTON_LEFT_Y + 2;
arrow[3].pixmap = gc_pixmap_load("crane/arrow_right.png");
arrow[3].x = CRANE_BUTTON_RIGHT_X;
arrow[3].y = CRANE_BUTTON_RIGHT_Y - 2;
for (i = 0 ; i < 4 ; i++) {
item_arrow = gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type(),
"pixbuf", arrow[i].pixmap,
"x", arrow[i].x,
"y", arrow[i].y,
"anchor", GTK_ANCHOR_NW,
NULL);
gtk_signal_connect(GTK_OBJECT(item_arrow), "event",
(GtkSignalFunc) arrow_event, GINT_TO_POINTER(i));
gtk_signal_connect(GTK_OBJECT(item_arrow), "event",
(GtkSignalFunc) gc_item_focus_event,
NULL);
gdk_pixbuf_unref( arrow[i].pixmap);
}
}
// Draw the red hands object which highlight the selected object
static void draw_redhands() {
GdkPixbuf *pixmap;
/* Initialize the rope (Warning, it's static and freed in crane_destroy_all_items) */
crane_rope = gnome_canvas_points_new(2);
crane_rope->coords[0] = 5;
crane_rope->coords[1] = CRANE_BUTTON_LEFT_Y;
crane_rope->coords[2] = 5;
crane_rope->coords[3] = CRANE_BUTTON_LEFT_Y;
crane_rope_item = gnome_canvas_item_new (boardRootItem,
gnome_canvas_line_get_type(),
"points", crane_rope,
"fill_color", "darkblue",
"width_units", (double) 1,
"width_pixels", (guint) 7,
NULL);
pixmap = gc_pixmap_load("crane/selected.png");
red_hands = gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type(),
"pixbuf", pixmap,
"x", (double) 5,
"y", (double) 5,
"width", (double) (CRANE_FRAME_IMAGE_SIZE + 5),
"height", (double) (CRANE_FRAME_IMAGE_SIZE + 5),
"width_set", TRUE,
"height_set", TRUE,
"anchor", GTK_ANCHOR_NW,
NULL);
gdk_pixbuf_unref(pixmap);
gnome_canvas_item_hide(red_hands);
}
// Draw the drak frame (horizontal and vertical lines) that helps positionning elements
static void draw_frame(int x, int y) {
GnomeCanvasItem *item_frame = NULL;
int i;
GnomeCanvasPoints *track;
track = gnome_canvas_points_new(2);
for (i = 1 ; i < CRANE_FRAME_COLUMN ; i++) {
track->coords[0] = x + (i * CRANE_FRAME_CELL);
track->coords[1] = y + CRANE_FRAME_BORDER;
track->coords[2] = x + i * CRANE_FRAME_CELL;
track->coords[3] = y + (CRANE_FRAME_LINE * CRANE_FRAME_CELL) - CRANE_FRAME_BORDER;
item_frame = gnome_canvas_item_new (boardRootItem,
gnome_canvas_line_get_type (),
"points", track,
"width_pixels", 1,
"fill_color", "black",
NULL);
}
for (i = 1 ; i < CRANE_FRAME_LINE ; i++) {
track->coords[0] = x + CRANE_FRAME_BORDER;
track->coords[1] = y + (i * CRANE_FRAME_CELL);
track->coords[2] = x + (CRANE_FRAME_COLUMN * CRANE_FRAME_CELL) - CRANE_FRAME_BORDER;
track->coords[3] = y + (i * CRANE_FRAME_CELL);
item_frame = gnome_canvas_item_new (boardRootItem,
gnome_canvas_line_get_type (),
"points", track,
"width_pixels", 1,
"fill_color", "black",
NULL);
}
gnome_canvas_points_free(track);
}
// Place randomly nb_element (depending on the level) elements on the frame.
// if active is set, the elements are connected to their function, that's for the bottom left frame
// if active is off, the elements are unmovable, that's for the top right model
static void place_item(int x, int y, int active) {
GdkPixbuf *pixmap;
GnomeCanvasItem *item_image = NULL;
int i;
int valeur;
for (i = 0 ; i < CRANE_FRAME_LINE * CRANE_FRAME_COLUMN ; i ++) {
if (active) {
valeur = list_game[i];
} else {
valeur = list_answer[i];
}
// Empty cell
if (valeur == -1)
continue;
pixmap = gc_pixmap_load(imageList[valeur]);
item_image = gnome_canvas_item_new (boardRootItem,
gnome_canvas_pixbuf_get_type (),
"pixbuf", pixmap,
"x", (double) (x + 5 + ((i % CRANE_FRAME_COLUMN) * CRANE_FRAME_CELL)),
"y", (double) (y + 5 + (floor(i / CRANE_FRAME_COLUMN) * CRANE_FRAME_CELL)),
NULL);
gdk_pixbuf_unref( pixmap);
if (active)
gtk_signal_connect(GTK_OBJECT(item_image), "event", (GtkSignalFunc) item_event, NULL);
}
// 'Focus' given to the last one
if (active)
select_item(item_image);
}
static guint smooth_move(move_object *move) {
double dx1, dy1, dx2, dy2;
if (nb_move == 0) {
moving = TRUE;
nb_move = move->nb;
}
gnome_canvas_item_get_bounds(red_hands, &dx1, &dy1, &dx2, &dy2);
crane_rope->coords[0] = (dx1 + dx2) / 2;
crane_rope->coords[1] = CRANE_ROPE_Y;
crane_rope->coords[2] = (dx1 + dx2) / 2;
crane_rope->coords[3] = (dy1 + dy2) / 2;
gnome_canvas_item_set (crane_rope_item,
"points", crane_rope,
NULL);
gnome_canvas_item_move(selected_item, move->x, move->y);
gnome_canvas_item_move(red_hands, move->x, move->y);
nb_move--;
if (nb_move == 0) {
moving = FALSE;
return FALSE;
}
return TRUE;
}
int is_allowed_move (double dx, double dy, int direction) {
if ((direction == LEFT) && (dx > (CRANE_FRAME_X + CRANE_FRAME_CELL)))
return 1;
else if ((direction == RIGHT) && (dx < (CRANE_FRAME_X + ((CRANE_FRAME_COLUMN - 1) * CRANE_FRAME_CELL))))
return 1;
else if ((direction == UP) && (dy > (CRANE_FRAME_Y + CRANE_FRAME_CELL)))
return 1;
else if ((direction == DOWN) && (dy < (CRANE_FRAME_Y + ((CRANE_FRAME_LINE - 1) * CRANE_FRAME_CELL))))
return 1;
return 0;
}
int get_item_index (double dx, double dy) {
return (((dx - CRANE_FRAME_X - 5) / CRANE_FRAME_CELL) + ((dy - CRANE_FRAME_Y - 5) / CRANE_FRAME_CELL * CRANE_FRAME_COLUMN));
}
void shuffle_list(int list[], int size) {
int i;
int rand1, rand2, buffer;
for (i = 0 ; i < size ; i++) {
rand1 = g_random_int_range(0, size-1);
rand2 = g_random_int_range(0, size-1);
buffer = list[rand2];
list[rand2] = list[rand1];
list[rand1] = buffer;
}
}
static void select_item(GnomeCanvasItem *item) {
double dx1, dy1, dx2, dy2;
// Use of gnome_canvas_item_affine_absolute must be better
gnome_canvas_item_hide(red_hands);
// Place redhands in (O;O)
gnome_canvas_item_get_bounds(red_hands, &dx1, &dy1, &dx2, &dy2);
gnome_canvas_item_move(red_hands, -(dx1), -(dy1));
// Place redhands 'around' the selected item
gnome_canvas_item_get_bounds(item, &dx1, &dy1, &dx2, &dy2);
gnome_canvas_item_move(red_hands, dx1 - 1 , dy1 - 1);
gnome_canvas_item_show(red_hands);
crane_rope->coords[0] = (dx1 + dx2) / 2;
crane_rope->coords[1] = CRANE_ROPE_Y;
crane_rope->coords[2] = (dx1 + dx2) / 2;
crane_rope->coords[3] = (dy1 + dy2) / 2;
gnome_canvas_item_set (crane_rope_item,
"points", crane_rope,
NULL);
selected_item = item;
}