/* gcompris - reversecount.c * * Copyright (C) 2002, 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 static GcomprisBoard *gcomprisBoard = NULL; static gboolean board_paused = TRUE; static gint animate_id = 0; static void start_board (GcomprisBoard *agcomprisBoard); static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str); 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); #define NORTH 1 #define WEST 2 #define SOUTH 4 #define EAST 8 #define TUX_TO_BORDER_GAP 10 static GooCanvasItem *boardRootItem = NULL; static void process_ok(void); static void process_error(void); static GooCanvasItem *reversecount_create_item(GooCanvasItem *parent); static void reversecount_destroy_all_items(void); static void reversecount_next_level(void); static gboolean item_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gint *dice_index); static GooCanvasItem *display_item_at(gchar *imagename, int block); static void display_random_fish(); static void create_clock(double x, double y, int value); static void update_clock(int value); static gboolean animate_tux(gpointer data); static void rotate_tux(GooCanvasItem *tuxitem, gint direction, gdouble scale); static void move_item_at(GooCanvasItem *item, int block, double ratio); static int number_of_item = 0; static int number_of_item_x = 0; static int number_of_item_y = 0; static int errors = 0; static int number_of_dices = 0; static int max_dice_number = 0; static int number_of_fish = 0; static int tux_index = 0; static int tux_destination = 0; static int fish_index = 0; static int animate_speed = 0; #define ANIMATE_SPEED 800 static gdouble tux_ratio = 0; static int dicevalue_array[10]; static GooCanvasItem *fishItem; static GooCanvasItem *tuxItem; static GooCanvasItem *tuxRootItem; static GooCanvasItem *clock_image_item; // List of images to use in the game static gchar *imageList[] = { "reversecount/baleine.svgz", "reversecount/phoque.svgz", "reversecount/ourspolaire.svgz", "reversecount/morse.svgz", "reversecount/elephant_mer.svgz", "reversecount/epaulard.svgz", "reversecount/narval.svgz", }; #define NUMBER_OF_IMAGES 10 // List of fish to use in the game static gchar *fishList[] = { "reversecount/blueking2_0.png", "reversecount/butfish_0.png", "reversecount/cichlid1_0.png", "reversecount/cichlid4_0.png", "reversecount/collaris_0.png", "reversecount/discus2_0.png", "reversecount/discus3_0.png", "reversecount/eel_0.png", "reversecount/f00_0.png", "reversecount/f01_0.png", "reversecount/f02_0.png", "reversecount/f03_0.png", "reversecount/f04_0.png", "reversecount/f05_0.png", "reversecount/f06_0.png", "reversecount/f07_0.png", "reversecount/f08_0.png", "reversecount/f09_0.png", "reversecount/f10_0.png", "reversecount/f11_0.png", "reversecount/f12_0.png", "reversecount/f13_0.png", "reversecount/manta_0.png", "reversecount/newf1_0.png", "reversecount/QueenAngel_0.png", "reversecount/shark1_0.png", "reversecount/six_barred_0.png", "reversecount/teeth_0.png" }; #define NUMBER_OF_FISHES 27 /* Description of this plugin */ static BoardPlugin menu_bp = { NULL, NULL, "Reverse count", "Practice substraction with a funny game", "Bruno Coudoin ", NULL, NULL, NULL, NULL, start_board, pause_board, end_board, is_our_board, key_press, NULL, set_level, NULL, NULL, NULL, NULL }; /* * Main entry point mandatory for each Gcompris's game * --------------------------------------------------- * */ GET_BPLUGIN_INFO(reversecount) /* * in : boolean TRUE = PAUSE : FALSE = CONTINUE * */ static void pause_board (gboolean pause) { if(gcomprisBoard==NULL) return; if(gamewon == TRUE && pause == FALSE) /* the game is won */ { game_won(); } else if(gamewon == FALSE && pause == FALSE) /* the game is lost */ reversecount_next_level(); board_paused = pause; } /* */ static void start_board (GcomprisBoard *agcomprisBoard) { if(agcomprisBoard!=NULL) { gcomprisBoard=agcomprisBoard; /* disable im_context */ gcomprisBoard->disable_im_context = TRUE; gcomprisBoard->level=1; gcomprisBoard->maxlevel=7; 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(10, -1, 0.7); reversecount_next_level(); gamewon = FALSE; pause_board(FALSE); } } /* ======================================= */ static void end_board () { if(gcomprisBoard!=NULL) { pause_board(TRUE); reversecount_destroy_all_items(); } gcomprisBoard = NULL; } /* ======================================= */ static void set_level (guint level) { if(gcomprisBoard!=NULL) { gcomprisBoard->level=level; gcomprisBoard->sublevel=1; reversecount_next_level(); } } /* ======================================= */ gboolean is_our_board (GcomprisBoard *gcomprisBoard) { if (gcomprisBoard) { if(g_ascii_strcasecmp(gcomprisBoard->type, "reversecount")==0) { /* Set the plugin entry */ gcomprisBoard->plugin=&menu_bp; return TRUE; } } return FALSE; } /* ======================================= */ gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str) { if(!gcomprisBoard) return FALSE; /* Add some filter for control and shift key */ switch (keyval) { /* Avoid all this keys to be interpreted by this game */ case GDK_Shift_L: case GDK_Shift_R: case GDK_Control_L: case GDK_Control_R: case GDK_Caps_Lock: case GDK_Shift_Lock: case GDK_Meta_L: case GDK_Meta_R: case GDK_Alt_L: case GDK_Alt_R: case GDK_Super_L: case GDK_Super_R: case GDK_Hyper_L: case GDK_Hyper_R: case GDK_Num_Lock: return FALSE; case GDK_KP_Enter: case GDK_Return: process_ok(); return TRUE; } return TRUE; } static void process_error() { gc_sound_play_ogg ("sounds/crash.wav", NULL); errors--; if(errors==0) { gamewon = FALSE; reversecount_destroy_all_items(); gc_bonus_display(gamewon, GC_BONUS_SMILEY); } else { update_clock(errors); } } /* ======================================= */ static void process_ok() { guint i; tux_destination = tux_index; for(i=0; i= number_of_item) tux_destination = tux_destination - (number_of_item); // Do not allow going at a position after the fish if((tux_destination > fish_index) || (tux_destination == tux_index)) { process_error(); return; } if(!animate_id) { animate_id = g_timeout_add (animate_speed, animate_tux, NULL); } } /*-------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------*/ /* set initial values for the next level */ static void reversecount_next_level() { gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas), imageList[gcomprisBoard->level-1]); reversecount_destroy_all_items(); gamewon = FALSE; /* Select level difficulty */ switch(gcomprisBoard->level) { case 1: number_of_item_x = 5; number_of_item_y = 5; number_of_dices = 1; max_dice_number = 3; number_of_fish = 3; break; case 2: number_of_item_x = 5; number_of_item_y = 5; number_of_dices = 1; max_dice_number = 6; number_of_fish = 6; break; case 3: number_of_item_x = 6; number_of_item_y = 6; number_of_dices = 1; max_dice_number = 9; number_of_fish = 6; break; case 4: number_of_item_x = 8; number_of_item_y = 6; number_of_dices = 1; max_dice_number = 3; number_of_fish = 6; break; case 5: number_of_item_x = 8; number_of_item_y = 6; number_of_dices = 2; max_dice_number = 6; number_of_fish = 10; break; case 6: number_of_item_x = 8; number_of_item_y = 8; number_of_dices = 2; max_dice_number = 9; number_of_fish = 10; break; default: number_of_item_x = 10; number_of_item_y = 10; number_of_dices = 3; max_dice_number = 9; number_of_fish = 10; break; } animate_speed = ANIMATE_SPEED - gcomprisBoard->level * 60; number_of_item = number_of_item_x * 2 + (number_of_item_y - 2) * 2; /* Try the next level */ reversecount_create_item(goo_canvas_get_root_item(gcomprisBoard->canvas)); gc_bar_set_level(gcomprisBoard); } /* ==================================== */ /* Destroy all the items */ static void reversecount_destroy_all_items() { gc_timer_end(); if(boardRootItem!=NULL) goo_canvas_item_remove(boardRootItem); boardRootItem = NULL; } /* ==================================== */ static GooCanvasItem *reversecount_create_item(GooCanvasItem *parent) { gint i, j, d; GooCanvasItem *item = NULL; gdouble block_width, block_height; gdouble dice_area_x; gdouble xratio, yratio; GcomprisProperties *properties = gc_prop_get(); RsvgHandle *svg_handle; boardRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas), NULL); block_width = BOARDWIDTH/number_of_item_x; block_height = (BOARDHEIGHT-BARHEIGHT)/number_of_item_y; /* Timer is not requested */ if(properties->timer > 0) { errors = number_of_dices + 4 - (MIN(properties->timer, 4)); create_clock(BOARDWIDTH - block_width - 100, BOARDHEIGHT - block_height - 100 - BARHEIGHT, errors) ; } else { errors = -1; } // Ice blocks svg_handle = gc_rsvg_load("reversecount/iceblock.svgz"); RsvgDimensionData rsvg_dimension; rsvg_handle_get_dimensions (svg_handle, &rsvg_dimension); xratio = block_width / rsvg_dimension.width; yratio = block_height / rsvg_dimension.height; for(i=0; i= number_of_item) fish_index = fish_index - (number_of_item); fishItem = display_item_at(fishList[g_random_int()%NUMBER_OF_FISHES], fish_index); } /* ==================================== */ /** * Display given imagename on the given ice block. */ static GooCanvasItem* display_item_at(gchar *imagename, int block) { double block_width, block_height; double xratio, yratio; GooCanvasItem *item; GdkPixbuf *pixmap; GdkPixbuf *pixmap2; int i,j; block_width = BOARDWIDTH/number_of_item_x; block_height = (BOARDHEIGHT-BARHEIGHT)/number_of_item_y; if(block < number_of_item_x) { // Upper line g_warning(" // Upper line\n"); i = block_width * block; j = 0; } else if(block < number_of_item_x + number_of_item_y - 2) { // Right line g_warning(" // Right line\n"); i = block_width * (number_of_item_x - 1); j = block_height * (block - (number_of_item_x-1)); } else if(block < number_of_item_x*2 + number_of_item_y - 2) { // Bottom line g_warning(" // Bottom line\n"); i = block_width * (number_of_item_x - (block- (number_of_item_x+number_of_item_y-1))-2); j = block_height * (number_of_item_y-1); } else { // Left line g_warning(" // Left line\n"); i = 0; j = block_height * (number_of_item_y - (block - (number_of_item_x*2 + number_of_item_y-4))); } g_warning("display_tux %d i=%d j=%d\n", block, i, j); /* Calculation to thrink the item while keeping the ratio */ pixmap = gc_pixmap_load(imagename); xratio = block_width / (gdk_pixbuf_get_width (pixmap) + TUX_TO_BORDER_GAP); yratio = block_height / (gdk_pixbuf_get_height(pixmap) + TUX_TO_BORDER_GAP); xratio = yratio = MIN(xratio, yratio); pixmap2 = gdk_pixbuf_scale_simple(pixmap, gdk_pixbuf_get_width (pixmap) * xratio, gdk_pixbuf_get_height(pixmap) * xratio, GDK_INTERP_BILINEAR); #if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24 gdk_pixbuf_unref(pixmap); #else g_object_unref(pixmap); #endif item = goo_canvas_image_new (boardRootItem, pixmap2, i + (block_width - (gdk_pixbuf_get_width (pixmap2))) / 2, j + (block_height - (gdk_pixbuf_get_height (pixmap2))) / 2, NULL); #if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24 gdk_pixbuf_unref(pixmap2); #else g_object_unref(pixmap2); #endif return(item); } /** * Move given GooCanvasItem on the given ice block. */ static void move_item_at(GooCanvasItem *item, int block, double ratio) { double block_width, block_height; int i,j; GooCanvasBounds bounds; block_width = BOARDWIDTH/number_of_item_x; block_height = (BOARDHEIGHT-BARHEIGHT)/number_of_item_y; if(block < number_of_item_x) { // Upper line g_warning(" // Upper line\n"); i = block_width * block; j = 0; } else if(block < number_of_item_x + number_of_item_y - 2) { // Right line g_warning(" // Right line\n"); i = block_width * (number_of_item_x - 1); j = block_height * (block - (number_of_item_x-1)); } else if(block < number_of_item_x*2 + number_of_item_y - 2) { // Bottom line g_warning(" // Bottom line\n"); i = block_width * (number_of_item_x - (block- (number_of_item_x+number_of_item_y-1))-2); j = block_height * (number_of_item_y-1); } else { // Left line g_warning(" // Left line\n"); i = 0; j = block_height * (number_of_item_y - (block - (number_of_item_x*2 + number_of_item_y-4))); } g_warning("move_item_at %d i=%d j=%d\n", block, i, j); goo_canvas_item_get_bounds(item, &bounds); goo_canvas_item_animate(item, i, j, 1.0, 0, TRUE, animate_speed, 40, GOO_CANVAS_ANIMATE_FREEZE); } /* ==================================== */ 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) { // the current board is finished : bail out gcomprisBoard->level = gcomprisBoard->maxlevel; return; } gc_sound_play_ogg ("sounds/bonus.wav", NULL); } reversecount_next_level(); } /* ==================================== */ /** * Increment the dices when they are clicked */ static gboolean item_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gint *dice_index) { gchar *str; RsvgHandle *rsvg_handle; gint i = *dice_index; if(board_paused) return FALSE; switch(event->button) { case 1: if(dicevalue_array[i]++ >= max_dice_number) dicevalue_array[i] = (number_of_dices==1 ? 1 : 0); break; case 2: case 3: if(dicevalue_array[i]-- == (number_of_dices==1 ? 1 : 0)) dicevalue_array[i] = max_dice_number; break; default: break; } str = g_strdup_printf("reversecount/dice%d.svgz", dicevalue_array[i]); rsvg_handle = gc_rsvg_load(str); g_object_set (item, "svg-handle", rsvg_handle, NULL); gc_item_focus_init(item, NULL); g_object_unref(rsvg_handle); g_free(str); return FALSE; } /* * Clock management */ static void create_clock(double x, double y, int value) { GdkPixbuf *pixmap = NULL; char *str = NULL; if(value<0) return; str = g_strdup_printf("%s%d.png", "timers/clock",value); pixmap = gc_skin_pixmap_load(str); clock_image_item = goo_canvas_image_new (boardRootItem, pixmap, x, y, NULL); #if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24 gdk_pixbuf_unref(pixmap); #else g_object_unref(pixmap); #endif g_free(str); } static void update_clock(int value) { GdkPixbuf *pixmap = NULL; char *str = NULL; if(value<0) return; str = g_strdup_printf("%s%d.png", "timers/clock",value); pixmap = gc_skin_pixmap_load(str); g_object_set (clock_image_item, "pixbuf", pixmap, NULL); #if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24 gdk_pixbuf_unref(pixmap); #else g_object_unref(pixmap); #endif g_free(str); } static gboolean animate_tux(gpointer data) { // Move tux tux_index++; move_item_at(tuxRootItem, tux_index, tux_ratio); g_warning("=========== tux_index=%d tux_destination=%d fish_index=%d\n", tux_index, tux_destination, fish_index); // Wrapping if(tux_index >= number_of_item) tux_index = tux_index - (number_of_item); /* Calculate which tux should be displayed */ if(tux_index