/* gcompris - magic_hat.c * * Copyright (C) 2006, 2008 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 "gcompris/gcompris.h" #include #define MH_FRAME1_X 420 // Coordonates for the first frame (first operand of the operation) #define MH_FRAME1_Y 60 #define MH_FRAME2_X 420 // Coordonates for the second frame (second operand of the operation) #define MH_FRAME2_Y 200 #define MH_FRAME_PLAYER_X 420 // Coordonates for the 'player' frame #define MH_FRAME_PLAYER_Y 380 #define MH_HAT_X 190 // Coordonates for the hat #define MH_HAT_Y 90 #define MH_HAT_HEIGHT 250 #define MH_HAT_WIDTH 130 #define POINT 0 // Which hat to draw ? The one with an interrogation point or #define STARS 1 // the one with stars ? #define NORMAL 0 #define EMPTY 1 #define UNDERHAT 2 #define DYNAMIC 3 #define MAX_ITEM 10 // Max number for an item in a list #define MAX_LIST 3 // Max number of list of items #define ITEM_SIZE 30 // Items are squares (or square-included) #define SPACE_BETWEEN_ITEMS 5 #define MODE_MINUS 0 #define MODE_PLUS 1 #define DEFAULT_MODE MODE_MINUS // Types // This structure describes a frame (there are 3 of them on the board) typedef struct { int id; double coord_x; double coord_y; int nb_stars[MAX_LIST]; int array_star_type[MAX_LIST][MAX_ITEM]; GooCanvasItem *array_item[MAX_LIST][MAX_ITEM]; } frame; // This structure decribes a movement typedef struct { int i; // i index in array_item int j; // j index in array_item double dx; double dy; int nb; // how much of x/y movement (to give a smooth effect) ? int frame; // number of the concerned frame (1 or 2) } move_object; // Global variables static GcomprisBoard *gcomprisBoard = NULL; static gboolean board_paused = TRUE; static GooCanvasItem *boardRootItem = NULL; static gint timer_id = 0; static gint board_mode = DEFAULT_MODE; static gint hat_event_id; // value returned by g_signal_connect. Used by gtk_signal_disconnect static GooCanvasItem *hat; static frame frame1; static frame frame2; static frame frame_player; // 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 process_ok (); static void set_level (guint level); static int gamewon; static void game_won(void); static GooCanvasItem *magic_hat_create_item(); static void magic_hat_destroy_all_items(void); static void magic_hat_next_level(void); // magic_hat functions static void draw_frame(frame *); static void draw_table(void); static void draw_hat(GooCanvasItem *hat, int); static void place_item(frame *, int); static gboolean hat_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data); static gboolean item_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data); static int nb_list(); static gint smooth_move(move_object *); static gint move_stars(frame *); static gint close_hat(); /* Description of this plugin */ static BoardPlugin menu_bp = { NULL, NULL, "Give the result of an operation.", "Click on the bottom left stars to give the result of the operation.", "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(magic_hat) static void pause_board (gboolean pause) { if (gcomprisBoard == NULL) return; if (timer_id) { g_source_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) { gcomprisBoard = agcomprisBoard; gcomprisBoard->level = 1; gcomprisBoard->maxlevel = 9; gcomprisBoard->sublevel = 1; gcomprisBoard->number_of_sublevel = 1; // Go to next level after this number of 'play' gc_bar_set(GC_BAR_LEVEL); gc_bar_location(20, -1, 0.7); if (strcmp(gcomprisBoard->mode, "minus") == 0) board_mode = MODE_MINUS; else if (strcmp(gcomprisBoard->mode, "plus") == 0) board_mode = MODE_PLUS; else board_mode = DEFAULT_MODE; gc_set_default_background(goo_canvas_get_root_item(gcomprisBoard->canvas)); magic_hat_next_level(); gamewon = FALSE; pause_board(FALSE); } } static void end_board () { if (timer_id) { g_source_remove (timer_id); timer_id = 0; } if (gcomprisBoard != NULL) { pause_board(TRUE); magic_hat_destroy_all_items(); } gcomprisBoard = NULL; } // Check if player has won static void process_ok() { int i; int ok = TRUE; if (board_mode == MODE_MINUS) { for (i = 0 ; i < nb_list() ; i++) { if (frame1.nb_stars[i] != (frame2.nb_stars[i] + frame_player.nb_stars[i])) ok = FALSE; } } else { for (i = 0 ; i < nb_list() ; i++) { if (frame_player.nb_stars[i] != (frame1.nb_stars[i] + frame2.nb_stars[i])) ok = FALSE; } } if (ok) { gamewon = TRUE; gc_sound_play_ogg ("sounds/bonus.wav", NULL); gc_bonus_display(gamewon, GC_BONUS_FLOWER); } } /* ======================================= */ static void set_level (guint level) { if (gcomprisBoard != NULL) { gcomprisBoard->level = level; gcomprisBoard->sublevel = 1; magic_hat_next_level(); } } /* ======================================= */ static gboolean is_our_board (GcomprisBoard *gcomprisBoard) { if (gcomprisBoard) { if (g_ascii_strcasecmp(gcomprisBoard->type, "magic_hat") == 0) { /* Set the plugin entry */ gcomprisBoard->plugin = &menu_bp; return TRUE; } } return FALSE; } /*-------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------*/ /* set initial values for the next level */ static void magic_hat_next_level() { magic_hat_destroy_all_items(); gamewon = FALSE; /* Try the next level */ magic_hat_create_item(); gc_bar_set_level(gcomprisBoard); } /* ==================================== */ /* Destroy all the items */ static void magic_hat_destroy_all_items() { if (timer_id) { g_source_remove (timer_id); timer_id = 0; } if(boardRootItem != NULL) goo_canvas_item_remove(boardRootItem); boardRootItem = NULL; } /* ==================================== */ static GooCanvasItem *magic_hat_create_item() { int i, j; GdkPixbuf *pixmap; int step; boardRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas), NULL); if (board_mode == MODE_MINUS) pixmap = gc_pixmap_load("magic_hat/magic_hat_minus_bg.png"); else pixmap = gc_pixmap_load("magic_hat/magic_hat_plus_bg.png"); goo_canvas_image_new (boardRootItem, pixmap, 0.0, 0.0, NULL); #if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24 gdk_pixbuf_unref(pixmap); #else g_object_unref(pixmap); #endif // Initialisation for frame1 frame1.id = 1; frame1.coord_x = MH_FRAME1_X; frame1.coord_y = MH_FRAME1_Y; // Initialisation for frame1 frame2.id = 2; frame2.coord_x = MH_FRAME2_X; frame2.coord_y = MH_FRAME2_Y; // Initialisation for frame1 frame_player.id = 3; frame_player.coord_x = MH_FRAME_PLAYER_X; frame_player.coord_y = MH_FRAME_PLAYER_Y; // The three frames of this activity : one for the sentence of the magician (top left), one for // the items out of the hat (top right), one for the answer of the payer (bottom right) draw_frame(&frame1); draw_frame(&frame2); draw_frame(&frame_player); // Description of the 9 levels for substraction // Level 1 : one list (yellow stars), from 2 to 4 stars in frame 1 // Level 2 : one list (yellow stars), from 2 to 7 stars in frame 1 // Level 3 : one list (yellow stars), from 2 to 10 stars in frame 1 // Level 4 : two lists (yellow and green stars), from 2 to 4 stars in frame 1 // Level 5 : two lists (yellow and green stars), from 2 to 7 stars in frame 1 // Level 6 : two lists (yellow and green stars), from 2 to 10 stars in frame 1 // Level 7 : three lists (yellow, green and blue stars), from 2 to 4 stars in frame 1 // Level 8 : three lists (yellow, green and blue stars), from 2 to 7 stars in frame 1 // Level 9 : three lists (yellow, green and blue stars), from 2 to 10 stars in frame 1 // // Description of the 9 levels for addition // Level 1 : one list (yellow stars), from 2 to 4 for the total // Level 2 : one list (yellow stars), from 2 to 7 for the total // Level 3 : one list (yellow stars), from 2 to 10 for the total // Level 4 : two lists (yellow and green stars), from 2 to 4 for the total // Level 5 : two lists (yellow and green stars), from 2 to 7 for the total // Level 6 : two lists (yellow and green stars), from 2 to 10 for the total // Level 7 : three lists (yellow, green and blue stars), from 2 to 4 for the total // Level 8 : three lists (yellow, green and blue stars), from 2 to 7 for the total // Level 9 : three lists (yellow, green and blue stars), from 2 to 10 for the total step = 3; for (i = 0 ; i < nb_list() ; i++) { // Frame 1 if (board_mode == MODE_MINUS) frame1.nb_stars[i] = g_random_int_range(2, (1 + (step * nb_list()))); // Minimum 2 to avoid '0' value (which is not easy to understand for kids) else frame1.nb_stars[i] = g_random_int_range(1, (step * nb_list())); for (j = 0 ; j < frame1.nb_stars[i] ; j++) frame1.array_star_type[i][j] = i; for ( ; j < MAX_ITEM ; j++) frame1.array_star_type[i][j] = -1; // Frame 2 if (board_mode == MODE_MINUS) frame2.nb_stars[i] = g_random_int_range(1, frame1.nb_stars[i]); // Minimum 1 to avoid '0' else frame2.nb_stars[i] = g_random_int_range(1, ((step * nb_list()) - frame1.nb_stars[i] + 1)); for (j = 0 ; j < frame2.nb_stars[i] ; j++) frame2.array_star_type[i][j] = i; for ( ; j < MAX_ITEM ; j++) frame2.array_star_type[i][j] = -1; for (j = 0 ; j < MAX_ITEM ; j++) frame2.array_item[i][j] = goo_canvas_image_new (boardRootItem, NULL, 0, 0, NULL); // Player frame frame_player.nb_stars[i] = 0; for (j = 0 ; j < MAX_ITEM ; j++) frame_player.array_star_type[i][j] = -1; } if (board_mode == MODE_MINUS) { place_item(&frame1, NORMAL); // design the 'total' stars, with all the items place_item(&frame2, UNDERHAT); // design 'out' stars, all the items are hidden under the hat } else { place_item(&frame1, NORMAL); // design the first frame stars, with all the items place_item(&frame2, NORMAL); // design the second frame stars, with all the items } // The magic hat !! And its table // The hat is designed after the 'minus' items so that it hides them /* Place the lower left corner as the (0,0) coord for the animation */ hat = goo_canvas_image_new (boardRootItem, NULL, 0, -MH_HAT_HEIGHT, NULL); goo_canvas_item_translate(hat, MH_HAT_X, MH_HAT_Y + MH_HAT_HEIGHT); draw_hat(hat, STARS); draw_table(); return NULL; } // 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) gcomprisBoard->level = gcomprisBoard->maxlevel; } magic_hat_next_level(); } // Draw a frame with empty small squares static void draw_frame(frame *my_frame) { int i, j; double x = my_frame->coord_x; double y = my_frame->coord_y; GooCanvasPoints *track; track = goo_canvas_points_new(5); for (i = 0 ; i < nb_list() ; i++) { for (j = 0 ; j < MAX_ITEM ; j++) { track->coords[0] = x + (j * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); track->coords[1] = y + (i * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); track->coords[2] = x + (j * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)) + ITEM_SIZE; track->coords[3] = y + (i * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); track->coords[4] = x + (j * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)) + ITEM_SIZE; track->coords[5] = y + (i * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)) + ITEM_SIZE; track->coords[6] = x + (j * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); track->coords[7] = y + (i * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)) + ITEM_SIZE; track->coords[8] = x + (j * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); track->coords[9] = y + (i * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); goo_canvas_polyline_new (boardRootItem, FALSE, 0, "points", track, "line-width", 1.0, "fill-color", "#948d85", NULL); } } goo_canvas_points_unref(track); place_item(my_frame, EMPTY); } // Draw the table (line) static void draw_table() { GooCanvasPoints *track; track = goo_canvas_points_new(2); track->coords[0] = MH_HAT_X; track->coords[1] = MH_HAT_Y + MH_HAT_HEIGHT + 5; track->coords[2] = MH_HAT_X + MH_HAT_WIDTH; track->coords[3] = MH_HAT_Y + MH_HAT_HEIGHT + 5; goo_canvas_polyline_new (boardRootItem, FALSE, 0, "points", track, "line-width", 1.0, "fill-color", "black", NULL); goo_canvas_points_unref(track); } // Draw the hat static void draw_hat(GooCanvasItem *item, int type) { GdkPixbuf *image; if (type == STARS) image = gc_pixmap_load("magic_hat/hat.png"); else image = gc_pixmap_load("magic_hat/hat-point.png"); g_object_set(item, "pixbuf", image, NULL); #if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24 gdk_pixbuf_unref(image); #else g_object_unref(image); #endif if (type == STARS) { hat_event_id = g_signal_connect(item, "button_press_event", (GCallback) hat_event, NULL); gc_item_focus_init(item, NULL); } } // Place items on the board // frame * my_frame : which frame to create the items on ? // int type : four possible values // EMPTY => only grey stars, which symbolise an empty item // NORMAL => a coloured star // UNDERHAT => objects are not visible, they are localised under the hat // DYNAMIC => the items are made clicable (for the player frame) static void place_item(frame * my_frame, int type) { GooCanvasItem *item = NULL; int i, j; int k, nb_item; RsvgHandle *image; double item_x, item_y; double x, y; RsvgHandle *image_name[MAX_LIST]; RsvgHandle *image_star_clear = gc_rsvg_load("magic_hat/star-clear.svgz"); image_name[0] = gc_rsvg_load("magic_hat/star1.svgz"); image_name[1] = gc_rsvg_load("magic_hat/star2.svgz"); image_name[2] = gc_rsvg_load("magic_hat/star3.svgz"); x = my_frame->coord_x; y = my_frame->coord_y; for (i = 0 ; i < nb_list() ; i++) { for (j = 0 ; j < MAX_ITEM ; j++) { if ((j < my_frame->nb_stars[i]) && (type != EMPTY)) image = image_name[i]; else image = image_star_clear; if (type == UNDERHAT) { item_x = (MH_HAT_X + ((MH_HAT_WIDTH - ITEM_SIZE) / 2)); item_y = (MH_HAT_Y + MH_HAT_HEIGHT - 2 * ITEM_SIZE); } else { item_x = x + (j * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); item_y = y + (i * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)); } // If NORMAL, we have to create two items : the first one stays on the frame, the // other one moves to the hat if (type == NORMAL) nb_item = 2; else nb_item = 1; for (k = 0 ; k < nb_item ; k++) { item = goo_canvas_svg_new (boardRootItem, image, NULL); goo_canvas_item_translate(item, item_x, item_y); } if (type == DYNAMIC) g_signal_connect(item, "button_press_event", (GCallback) item_event, GINT_TO_POINTER(MAX_ITEM * i + j)); if (type == UNDERHAT || type == NORMAL) my_frame->array_item[i][j] = item; } } g_object_unref(image_star_clear); g_object_unref(image_name[0]); g_object_unref(image_name[1]); g_object_unref(image_name[2]); } /* When clicked, an star from the player frame changes * its appearance (grey or coloured) and the counter is re-evaluated */ static gboolean item_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data) { int index = GPOINTER_TO_INT(data); if (board_paused) return FALSE; if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1)) { if (frame_player.array_star_type[index / MAX_ITEM][index % MAX_ITEM] >= 0) { RsvgHandle *pixmap; // Desactivate the star frame_player.nb_stars[index / MAX_ITEM]--; frame_player.array_star_type[index / MAX_ITEM][index % MAX_ITEM] = -1; pixmap = gc_rsvg_load("magic_hat/star-clear.svgz"); g_object_set(item, "svg-handle", pixmap, NULL); g_object_unref(pixmap); } else { RsvgHandle *pixmap = NULL; // Activate the star frame_player.nb_stars[index / MAX_ITEM]++; frame_player.array_star_type[index / MAX_ITEM][index % MAX_ITEM] = index / MAX_ITEM; switch(index / MAX_ITEM) { case 0: pixmap = gc_rsvg_load("magic_hat/star1.svgz"); break; case 1: pixmap = gc_rsvg_load("magic_hat/star2.svgz"); break; case 2: pixmap = gc_rsvg_load("magic_hat/star3.svgz"); break; } g_object_set(item, "svg-handle", pixmap, NULL); g_object_unref(pixmap); } gc_sound_play_ogg ("sounds/bleep.wav", NULL); process_ok(); } return FALSE; } // When clicked, the hat rotates and a few items can go out of or into it // Then the hat go back in its previous position, and the game can start static gboolean hat_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data) { if (board_paused) return FALSE; if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1)) { // disconnect hat and hat_event, so that hat can not be clicked any more g_signal_handler_disconnect(hat, hat_event_id); // 'open' the hat goo_canvas_item_animate (item, 0, 0, 1, -30, FALSE, 40 * 20, 40, GOO_CANVAS_ANIMATE_FREEZE); // Make the items move from/out the hat, depending on the mode // Wait a few seconds between the two frames move_stars(&frame1); timer_id = g_timeout_add(1200, (GSourceFunc) move_stars, &frame2); // Wait again a few seconds before closing the hat. Then the game is ready to start timer_id = g_timeout_add(2600, (GSourceFunc) close_hat, NULL); } return FALSE; } // Return nb_list to be displayed, depending of the game level : // levels 1, 2 and 3 : one list // levels 4, 5 and 6 : two lists // levels 7, 8 and 9 : three lists static int nb_list() { if(gcomprisBoard == NULL) return 0; return (1 + (gcomprisBoard->level - 1) / MAX_LIST) ; } // Move all the stars of one frame to or from the hat static gint move_stars(frame *my_frame) { int i, j; move_object *my_move = NULL; gc_sound_play_ogg ("sounds/level.wav", NULL); for (i = 0 ; i < nb_list() ; i++) { for (j = 0 ; j < my_frame->nb_stars[i] ; j++) { if ((my_move = g_malloc(sizeof(move_object))) == NULL) { // Freed in function smooth_move g_error ("Malloc error in hat_event"); } my_move->i = i; my_move->j = j; my_move->nb = 20; my_move->dx = - ((my_frame->coord_x + (my_move->j * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)) - (MH_HAT_X + ((MH_HAT_WIDTH - ITEM_SIZE) / 2))) / my_move->nb); my_move->dy = - ((my_frame->coord_y + (my_move->i * (ITEM_SIZE + SPACE_BETWEEN_ITEMS)) - (MH_HAT_Y + MH_HAT_HEIGHT - 2 * ITEM_SIZE)) / my_move->nb); if (board_mode == MODE_MINUS && my_frame->id == 2) { my_move->dx = -my_move->dx; my_move->dy = -my_move->dy; } my_move->frame = my_frame->id; timer_id = g_timeout_add(50, (GSourceFunc) smooth_move, my_move); } } return FALSE; } // Close the hat, then the game can start static gint close_hat() { // 'close' the hat goo_canvas_item_animate (hat, 0, 0, 1, 30, FALSE, 40 * 20, 40, GOO_CANVAS_ANIMATE_FREEZE); draw_hat(hat, POINT); // draw an empty dynamic frame, each item is activable by left click // before this, the player_frame is not clicable place_item(&frame_player, DYNAMIC); return FALSE; } // Move a star smoothly from under the hat to its final location, on the minus frame static gint smooth_move(move_object *my_move) { if (!my_move->nb-- || boardRootItem == NULL) { g_free(my_move); return FALSE; } if (my_move->frame == 1) goo_canvas_item_translate(frame1.array_item[my_move->i][my_move->j], my_move->dx, my_move->dy); else goo_canvas_item_translate(frame2.array_item[my_move->i][my_move->j], my_move->dx, my_move->dy); return TRUE; }