/* gcompris - target.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 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 void game_won(void); #define VERTICAL_SEPARATION 30 #define HORIZONTAL_SEPARATION 30 #define TEXT_COLOR "white" static GooCanvasItem *boardRootItem = NULL; static GooCanvasItem *valueRootItem = NULL; static GooCanvasItem *speedRootItem = NULL; static GooCanvasItem *tooltipItem = NULL; static double wind_speed; static double ang; static GooCanvasItem *answer_item = NULL; static gchar answer_string[10]; static guint answer_string_index = 0; static GooCanvasItem *animate_item = NULL; static gint animate_id = 0; static gint animate_item_distance = 0; static gint animate_item_size = 0; static double animate_item_x = 0; static double animate_item_y = 0; #define MAX_DART_SIZE 20 #define MIN_DART_SIZE 3 static guint user_points = 0; /* * Functions Definition */ static void process_ok(void); static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str); static GooCanvasItem *target_create_item(GooCanvasItem *parent); static void target_destroy_all_items(void); static void target_next_level(void); static gboolean item_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data); static gboolean tooltip_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer gpoint); static gboolean animate_items(gpointer data); static void launch_dart(double item_x, double item_y); /* * distance to target, min wind speed, max wind speed, target1 width(center), * value1 (for target1), ... to target10, value10 * * a target width of 0 means no such target */ #define MAX_NUMBER_OF_TARGET 10 typedef struct { guint number_of_arrow; guint target_distance; guint target_min_wind_speed; guint target_max_wind_speed; gint target_width_value[MAX_NUMBER_OF_TARGET*2]; } TargetDefinition; /* * Definition of targets one line by level based on TargetDefinition */ static TargetDefinition targetDefinition[] = { { 3, 100, 2, 5, { 40, 5 , 80, 3, 150, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 5, 150, 2, 7, { 30, 10, 50, 5, 150, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 7, 200, 4, 9, { 20, 10, 40, 5, 60, 3, 150, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, { 7, 200, 5, 10, { 15, 100, 35, 50, 55, 10, 75, 5, 150, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} } }; #define NUMBER_OF_TARGET 4 #define TARGET_CENTER_X 235.0 #define TARGET_CENTER_Y 260.0 #define SPEED_CENTER_X 660.0 #define SPEED_CENTER_Y 125.0 static guint target_colors[] = { 0xAA0000FF, 0x00AA00FF, 0x0000AAFF, 0xAAAA00FF, 0x00AAAAFF, 0xAA00AAFF, 0xAA0000FF, 0x00AA00FF, 0x0000AAFF, 0xAA0000AF }; static guint number_of_arrow = 0; /* Description of this plugin */ static BoardPlugin menu_bp = { NULL, NULL, "Practice addition with a target game", "Hit the target and count your points", "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(target) /* * 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(); } if(pause) { if (animate_id) { g_source_remove (animate_id); animate_id = 0; } } else { if(animate_item) { animate_id = g_timeout_add (200, animate_items, NULL); } } 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=NUMBER_OF_TARGET; gcomprisBoard->sublevel=1; gcomprisBoard->number_of_sublevel=1; /* Go to next level after this number of 'play' */ gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas), "target/target_background.svgz"); gc_bar_set(GC_BAR_LEVEL); gc_bar_location(BOARDWIDTH-200, -1, 0.8); target_next_level(); gamewon = FALSE; pause_board(FALSE); } } /* ======================================= */ static void end_board () { if(gcomprisBoard!=NULL) { pause_board(TRUE); target_destroy_all_items(); } gcomprisBoard = NULL; } /* ======================================= */ static void set_level (guint level) { if(gcomprisBoard!=NULL) { gcomprisBoard->level=level; gcomprisBoard->sublevel=1; target_next_level(); } } /* ======================================= */ static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str) { guint c; if(!gcomprisBoard || gamewon == TRUE) 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; case GDK_Right: case GDK_Left: break; case GDK_BackSpace: if(answer_string_index>0) { answer_string_index--; answer_string[answer_string_index] = 0; } break; } c = tolower(keyval); /* Limit the user entry to 5 digits */ if(c>='0' && c<='9' && answer_string_index<5) { answer_string[answer_string_index++] = c; answer_string[answer_string_index] = 0; } if(answer_item) { gchar *tmpstr = g_strdup_printf(_("Points = %s"), answer_string); g_object_set(answer_item, "text", tmpstr, NULL); g_free(tmpstr); process_ok(); } return TRUE; } /* ======================================= */ static gboolean is_our_board (GcomprisBoard *gcomprisBoard) { if (gcomprisBoard) { if(g_ascii_strcasecmp(gcomprisBoard->type, "target")==0) { /* Set the plugin entry */ gcomprisBoard->plugin=&menu_bp; return TRUE; } } return FALSE; } /*-------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------*/ /* set initial values for the next level */ static void target_next_level() { gc_bar_set_level(gcomprisBoard); target_destroy_all_items(); gamewon = FALSE; /* Try the next level */ target_create_item(goo_canvas_get_root_item(gcomprisBoard->canvas)); } /* ==================================== */ /* Destroy all the items */ static void target_destroy_all_items() { if(boardRootItem!=NULL) goo_canvas_item_remove(boardRootItem); boardRootItem = NULL; if(speedRootItem!=NULL) goo_canvas_item_remove(speedRootItem); animate_item = NULL; answer_item = NULL; answer_string_index = 0; user_points = 0; speedRootItem = NULL; } /* * Display a random wind speed */ static void display_windspeed() { guint second = 0; guint needle_zoom = 15; gchar *tmpstr; GooCanvasPoints *canvasPoints; canvasPoints = goo_canvas_points_new (2); if(speedRootItem!=NULL) goo_canvas_item_remove(speedRootItem); speedRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas), NULL); /* Speed orientation */ second = g_random_int()%60; ang = second * M_PI / 30; /* Speed force */ wind_speed = targetDefinition[gcomprisBoard->level-1].target_min_wind_speed \ + g_random_int()%(targetDefinition[gcomprisBoard->level-1].target_max_wind_speed \ - targetDefinition[gcomprisBoard->level-1].target_min_wind_speed); canvasPoints->coords[0]=SPEED_CENTER_X; canvasPoints->coords[1]=SPEED_CENTER_Y; canvasPoints->coords[2]=SPEED_CENTER_X + wind_speed * sin(ang) * needle_zoom; canvasPoints->coords[3]=SPEED_CENTER_Y - wind_speed * cos(ang) * needle_zoom; double w = 4.0; goo_canvas_polyline_new (speedRootItem, FALSE, 0, "points", canvasPoints, "stroke-color-rgba", 0Xa05a2cffL, "fill-color-rgba", 0Xa05a2cffL, "line-width", w, "end-arrow", TRUE, "arrow-tip-length", 7.0, "arrow-length", 5.0, "arrow-width", 4.0, NULL); goo_canvas_points_unref(canvasPoints); /* Draw the center of the speedometer */ goo_canvas_ellipse_new (speedRootItem, SPEED_CENTER_X, SPEED_CENTER_Y, 10.0, 10.0, "fill_color_rgba", 0Xa05a2cffL, "stroke-color", "black", "line-width", (double)2, NULL); tmpstr = g_strdup_printf(_("Wind speed = %d\nkilometers/hour"), (guint)wind_speed); goo_canvas_text_new (speedRootItem, tmpstr, (double) SPEED_CENTER_X, (double) SPEED_CENTER_Y + 110, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_medium, "fill-color", "white", NULL); g_free(tmpstr); } /* ==================================== */ static GooCanvasItem *target_create_item(GooCanvasItem *parent) { int i; gchar *tmpstr; GooCanvasItem *item = NULL; boardRootItem = goo_canvas_group_new (parent, NULL); valueRootItem = goo_canvas_group_new (boardRootItem, NULL); goo_canvas_item_translate(boardRootItem, TARGET_CENTER_X, TARGET_CENTER_Y); for(i=0; ilevel-1].target_width_value[i*2]>0) { item = \ goo_canvas_ellipse_new (boardRootItem, 0.0, 0.0, (double)targetDefinition[gcomprisBoard->level-1].target_width_value[i*2], (double)targetDefinition[gcomprisBoard->level-1].target_width_value[i*2], "fill_color_rgba", target_colors[i], "stroke-color", "black", "line-width", 1.0, NULL); goo_canvas_item_lower(item, NULL); g_signal_connect(item, "button-press-event", (GCallback) item_event, NULL); /* Display the value for this target */ tmpstr = g_strdup_printf("%d", targetDefinition[gcomprisBoard->level-1].target_width_value[i*2+1]); item = goo_canvas_text_new (valueRootItem, tmpstr, (double) 0, (double) targetDefinition[gcomprisBoard->level-1].target_width_value[i*2] - 10, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_medium, "fill-color", "white", NULL); g_free(tmpstr); g_signal_connect(item, "button-press-event", (GCallback) item_event, NULL); } } number_of_arrow = targetDefinition[gcomprisBoard->level-1].number_of_arrow; guint target_distance = targetDefinition[gcomprisBoard->level-1].target_distance; tmpstr = g_strdup_printf(ngettext("Distance to target = %d meter", "Distance to target = %d meters", target_distance), target_distance); goo_canvas_text_new (boardRootItem, tmpstr, (double) 0, (double) BOARDHEIGHT-TARGET_CENTER_Y -45, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_medium, "fill-color", "black", NULL); g_free(tmpstr); display_windspeed(); /* Tooltip */ tooltipItem = goo_canvas_text_new (boardRootItem, "", 150, -140, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", "white", NULL); return NULL; } /* ==================================== */ 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; gc_sound_play_ogg ("sounds/bonus.wav", NULL); } target_next_level(); } static gboolean bonus(gpointer data) { gc_bonus_display(gamewon, GC_BONUS_SMILEY); animate_id = 0; return(FALSE); } static void process_ok() { guint answer_points = atoi(answer_string); if(answer_item) { if(answer_points == user_points) { gamewon = TRUE; animate_id = g_timeout_add (200, bonus, NULL); } } } /* * Request score * */ static void request_score() { double x_offset = 390; double y_offset = 150; gchar *tmpstr; /* Set the maximum text to calc the background */ tmpstr = g_strdup_printf(_("Points = %s"), "00000"); answer_item = goo_canvas_text_new (boardRootItem, tmpstr, (double) x_offset, (double) y_offset, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", "white", NULL); g_free(tmpstr); GooCanvasBounds bounds; goo_canvas_item_get_bounds (answer_item, &bounds); goo_canvas_convert_to_item_space(goo_canvas_item_get_canvas(answer_item), answer_item, &bounds.x1, &bounds.y1); goo_canvas_convert_to_item_space(goo_canvas_item_get_canvas(answer_item), answer_item, &bounds.x2, &bounds.y2); int gap = 15; GooCanvasItem *item = \ goo_canvas_rect_new (boardRootItem, bounds.x1 - gap, bounds.y1 - gap, (bounds.x2 - bounds.x1) + gap*2, (bounds.y2 - bounds.y1) + gap*2, "stroke_color_rgba", 0xFFFFFFFFL, "fill_color_rgba", 0X5599FFCCL, "line-width", (double) 2, "radius-x", (double) 10, "radius-y", (double) 10, NULL); goo_canvas_item_raise(answer_item, item); /* Set the correct initial text */ tmpstr = g_strdup_printf(_("Points = %s"), answer_string); g_object_set(answer_item, "text", tmpstr, NULL); g_free(tmpstr); } static guint add_points(double x, double y) { guint i; double diametre; guint points = 0; // Calculate the distance diametre = sqrt(x*x+y*y); for(i=0; ilevel-1].target_width_value[i*2]) { points = targetDefinition[gcomprisBoard->level-1].target_width_value[i*2+1]; user_points += points; break; } } return points; } /* * Dart animation * */ static gboolean animate_items(gpointer data) { if(board_paused) return(FALSE); if(!animate_item) return(FALSE); // Apply the wind move animate_item_x = animate_item_x + wind_speed * sin(ang); animate_item_y = animate_item_y - wind_speed * cos(ang); g_object_set (animate_item, "center-x", (double)animate_item_x, "center-y", (double)animate_item_y, "radius-x", (double)animate_item_size, "radius-y", (double)animate_item_size, NULL); if(animate_item_size>MIN_DART_SIZE) animate_item_size--; if(--animate_item_distance == 0) { gc_sound_play_ogg ("sounds/brick.wav", NULL); // Calc the point for this dart guint points = add_points(animate_item_x, animate_item_y); // Add a tooltip on this dart to let the children // see how we count it g_signal_connect(animate_item, "enter_notify_event", (GCallback) tooltip_event, GINT_TO_POINTER(points)); g_signal_connect(animate_item, "leave_notify_event", (GCallback) tooltip_event, GINT_TO_POINTER(-1)); g_source_remove (animate_id); animate_id = 0; animate_item = NULL; // Change the wind for the next target display_windspeed(); } return(TRUE); } /* * */ static void launch_dart(double item_x, double item_y) { animate_item_x = item_x; animate_item_y = item_y; animate_item_size = MAX_DART_SIZE; animate_item_distance = targetDefinition[gcomprisBoard->level-1].target_distance/10; gc_sound_play_ogg ("sounds/line_end.wav", NULL); animate_item = goo_canvas_ellipse_new (boardRootItem, (double)item_x, (double)item_y, (double)MAX_DART_SIZE, (double)MAX_DART_SIZE, "fill_color_rgba", 0xFF80FFFF, "stroke-color", "white", "line-width", (double)1, NULL); /* Make sure the target values stay on top */ goo_canvas_item_lower(animate_item, valueRootItem); animate_id = g_timeout_add (200, animate_items, NULL); if(--number_of_arrow == 0) { request_score(); } } /* ==================================== */ static gboolean item_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data) { double item_x, item_y; if(board_paused) return FALSE; /* Is there already a dart on air */ if(number_of_arrow == 0 || animate_item) return FALSE; switch(event->button) { case 1: case 2: case 3: item_x = event->x; item_y = event->y; //goo_canvas_convert_to_item_space(item->parent, &item_x, &item_y); launch_dart(item_x, item_y); break; default: break; } return FALSE; } static gboolean tooltip_event (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer gpoint) { gint point = GPOINTER_TO_INT(gpoint); if (point >= 0) { gchar *tmpstr = g_strdup_printf("%d", point); g_object_set(tooltipItem, "text", tmpstr, NULL); g_free(tmpstr); } else { g_object_set(tooltipItem, "text", "", NULL); } return TRUE; }