/* gcompris - algebra_guesscount.c * * Copyright (C) 2001, 2008 Pascal Georges * * 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" 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 gint process_time_id = 0; static gboolean process_time(gpointer data); static void game_won(void); static void destroy_board(void); /* 4 levels : * 1evel 1 : 2 numbers and 1 operation * 1evel 2 : 3 numbers and 2 operations * 1evel 3 : 4 numbers and 3 operations * 1evel 4 : 5 numbers and 4 operations */ #define NUMBER_OF_SUBLEVELS 3 // 3 #define NUMBER_OF_LEVELS 4 // 4 #define MAX_NUMBER 5 #define TEXT_COLOR_FRONT "yellow" #define TEXT_COLOR_BACK "black" #define TEXT_RESULT_COLOR_FRONT "red" #define TEXT_RESULT_COLOR_BACK "orange" #define BLANK "___" #define NO_RESULT -1 #define BUTTON_WIDTH 81 #define BUTTON_HEIGHT 64 #define HORIZONTAL_SEPARATION 20 #define VERTICAL_SEPARATION 20 static const char oper_values[] = {'+', '-', 'x', ':', '='}; static const char *oper_images[] = {"plus", "minus", "by", "div", "equal"}; static const int num_values[] = {1,2,3,4,5,6,7,8,9,10,25,50,100}; #define NUM_VALUES 13 #define Y_OPE 20 #define Y_NUM 100 #define Y_ANS 200 #define X_NUM1 300 #define X_OPE 400 #define X_NUM2 500 #define X_EQUAL 600 #define X_RESULT 700 typedef struct _token token; struct _token { gboolean isNumber; gboolean isMoved; char oper; int num; int xOffset_original; int signal_id; GooCanvasItem * item; }; // contains the values, NUM OPER NUM OPER NUM etc. token token_value[MAX_NUMBER*2-1]; token * ptr_token_selected[MAX_NUMBER*2-1]; static const int y_equal_offset[] = {Y_ANS, Y_ANS+BUTTON_HEIGHT+VERTICAL_SEPARATION, Y_ANS+2*BUTTON_HEIGHT+2*VERTICAL_SEPARATION, Y_ANS+3*BUTTON_HEIGHT+3*VERTICAL_SEPARATION}; static const int x_token_offset[] = {X_NUM1,X_OPE,X_NUM2,X_OPE,X_NUM2,X_OPE,X_NUM2,X_OPE,X_NUM2}; static const int y_token_offset[] = {Y_ANS, Y_ANS,Y_ANS, Y_ANS+BUTTON_HEIGHT+VERTICAL_SEPARATION, Y_ANS+BUTTON_HEIGHT+VERTICAL_SEPARATION, Y_ANS+2*BUTTON_HEIGHT+2*VERTICAL_SEPARATION,Y_ANS+2*BUTTON_HEIGHT+2*VERTICAL_SEPARATION, Y_ANS+3*BUTTON_HEIGHT+3*VERTICAL_SEPARATION, Y_ANS+3*BUTTON_HEIGHT+3*VERTICAL_SEPARATION}; static char answer_oper[MAX_NUMBER-1]; static int answer_num_index[MAX_NUMBER]; static int token_count; static int result_to_find; /* ================================================================ */ static GooCanvasItem *boardRootItem = NULL; static GdkPixbuf * num_pixmap[NUM_VALUES]; static GdkPixbuf * oper_pixmap[5]; static GooCanvasItem *oper_item[4]; static GooCanvasItem *num_item[MAX_NUMBER]; static GooCanvasItem *equal_item[NUMBER_OF_LEVELS]; static GooCanvasItem *calcul_line_item[NUMBER_OF_LEVELS*2]; static GooCanvasItem *calcul_line_item_back[NUMBER_OF_LEVELS*2]; static GooCanvasItem *result_item_front, *result_item_back; static GooCanvasItem *algebra_guesscount_create_item(GooCanvasItem *parent); static void algebra_guesscount_destroy_all_items(void); static void algebra_guesscount_next_level(void); static gboolean item_event_num (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data); static gboolean item_event_oper (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data); static gboolean item_event_oper_moved (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data); static int generate_numbers(); static int token_result(); static char *background_images[NUMBER_OF_LEVELS] = { "algebra_guesscount/tiger1_RS.jpg", "algebra_guesscount/tigercub003.jpg", "algebra_guesscount/tigerdrink001.jpg", "algebra_guesscount/tigerplay001.jpg" }; /* Description of this plugin */ static BoardPlugin menu_bp = { NULL, NULL, "Guess operations", "Guess operations", "Pascal Georges pascal.georges1@free.fr>", 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(algebra_guesscount) /* ==================================== */ // in : boolean TRUE = PAUSE : FALSE = CONTINUE static void pause_board (gboolean pause){ if(gcomprisBoard==NULL) return; if(gamewon == TRUE && pause == FALSE) { game_won(); } board_paused = pause; } /* ==================================== */ static void start_board (GcomprisBoard *agcomprisBoard) { int i; gchar *str; if(agcomprisBoard!=NULL){ gcomprisBoard=agcomprisBoard; // load pixmap files g_warning("loading pixmaps in start_board\n"); for (i=0; iboarddir,num_values[i]); num_pixmap[i] = gc_pixmap_load(str); g_free(str); } for (i=0; i<5; i++) { str = g_strdup_printf("%s/%s.svg", gcomprisBoard->boarddir,oper_images[i]); oper_pixmap[i] = gc_pixmap_load(str); g_free(str); } gcomprisBoard->level=1; gcomprisBoard->maxlevel=NUMBER_OF_LEVELS; gcomprisBoard->sublevel=1; gcomprisBoard->number_of_sublevel=NUMBER_OF_SUBLEVELS; /* Go to next level after this number of 'play' */ gc_score_start(SCORESTYLE_NOTE, 10, 30, gcomprisBoard->number_of_sublevel); gc_bar_set(GC_BAR_LEVEL); gc_bar_location(10, -1, 0.8); algebra_guesscount_next_level(); gamewon = FALSE; pause_board(FALSE); } } /* ==================================== */ static void end_board () { if(gcomprisBoard!=NULL) { pause_board(TRUE); gc_score_end(); destroy_board(); algebra_guesscount_destroy_all_items(); } gcomprisBoard = NULL; } /* ==================================== */ static void set_level (guint level) { if(gcomprisBoard!=NULL) { gcomprisBoard->level=level; gcomprisBoard->sublevel=1; algebra_guesscount_next_level(); } } /* ==================================== */ static gboolean is_our_board (GcomprisBoard *gcomprisBoard) { if (gcomprisBoard) { if(g_ascii_strcasecmp(gcomprisBoard->type, "algebra_guesscount")==0) { /* Set the plugin entry */ gcomprisBoard->plugin=&menu_bp; return TRUE; } } return FALSE; } /* ==================================== */ /* set initial values for the next level */ static void algebra_guesscount_next_level() { gc_bar_set_level(gcomprisBoard); gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas), background_images[gcomprisBoard->level - 1]); algebra_guesscount_destroy_all_items(); gamewon = FALSE; token_count = 0; gc_score_set(gcomprisBoard->sublevel); /* Try the next level */ algebra_guesscount_create_item(goo_canvas_get_root_item(gcomprisBoard->canvas)); } /* ==================================== */ /* Destroy all the items */ static void algebra_guesscount_destroy_all_items() { if(boardRootItem!=NULL) goo_canvas_item_remove(boardRootItem); boardRootItem = NULL; } /* ==================================== */ static int token_result() { int result, i; if (token_count < 2) return NO_RESULT; g_assert(ptr_token_selected[0]->isNumber); result = num_values[ptr_token_selected[0]->num]; for (i=2; iisNumber); switch (ptr_token_selected[i-1]->oper) { case '+' : result += num_values[ptr_token_selected[i]->num]; break; case '-' : if (result - num_values[ptr_token_selected[i]->num] < 0) return NO_RESULT; result -= num_values[ptr_token_selected[i]->num]; break; case 'x' : result *= num_values[ptr_token_selected[i]->num]; break; case ':' : if (result%num_values[ptr_token_selected[i]->num] != 0) return NO_RESULT; result /= num_values[ptr_token_selected[i]->num]; break; default : g_warning("bug in token_result()\n"); break; } } return result; } /* ==================================== */ static void update_line_calcul() { int line; char str[12]; // finds which line has to be zeroed. if (token_count%2 == 0) line = (int)(token_count/2-1); else line = (int)(token_count/2); if(line==-1) return; sprintf(str, "%d",token_result()); g_object_set(calcul_line_item[line*2], "text", BLANK, NULL); g_object_set(calcul_line_item_back[line*2], "text", BLANK, NULL); if(line < gcomprisBoard->level-1) { /* No next line to update on last line */ g_object_set(calcul_line_item[line*2+1], "text", BLANK, NULL); g_object_set(calcul_line_item_back[line*2+1], "text", BLANK, NULL); } } /* ==================================== */ static int generate_numbers() { int i, r, j, result; gboolean minus, divide; for (i=0; ilevel+1; i++) { j = g_random_int_range(0,NUM_VALUES-1); answer_num_index[i] = j; } result = num_values[answer_num_index[0]]; for (i=0; ilevel; i++) { // + and x can always be chosen, but we must ensure - and / are valid minus = (result - num_values[answer_num_index[i+1]] >= 0); if(gcomprisBoard->level > 2 && num_values[answer_num_index[i+1]] <= 5) { /* Avoid div operator at lower level */ divide = (result % num_values[answer_num_index[i+1]] == 0); } else { divide = 0; } r = 2 + minus + divide; switch (g_random_int_range(1,r)) { case 1 : answer_oper[i] = '+'; result += num_values[answer_num_index[i+1]]; break; case 2 : // prevent result from getting too big and accept only < 10 bor the by operator if ( (result*num_values[answer_num_index[i+1]] < 1000 ) && ( num_values[answer_num_index[i+1]] < 10) ) { answer_oper[i] = 'x'; result *= num_values[answer_num_index[i+1]]; } else { answer_oper[i] = '+'; result += num_values[answer_num_index[i+1]]; } break; case 3 : if (minus) { answer_oper[i] = '-'; result -= num_values[answer_num_index[i+1]]; g_assert(result >= 0); } else { answer_oper[i] = ':'; g_assert(result%num_values[answer_num_index[i+1]] == 0); result /= num_values[answer_num_index[i+1]]; } break; case 4 : if ( g_random_int_range(0,1) == 0) { answer_oper[i] = '-'; result -= num_values[answer_num_index[i+1]]; g_assert(result >= 0); } else { answer_oper[i] = ':'; g_assert(result%num_values[answer_num_index[i+1]] == 0); result /= num_values[answer_num_index[i+1]]; } break; default : g_warning("Bug in guesscount\n"); break; } } return result; } /* ==================================== */ static GooCanvasItem *algebra_guesscount_create_item(GooCanvasItem *parent) { int i, xOffset, sid; char str[10]; result_to_find = generate_numbers(); boardRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas), NULL); // the intermediate result, line by line, when empty is BLANK for (i=0; ilevel; i++) { calcul_line_item_back[i*2] = goo_canvas_text_new (boardRootItem, BLANK, (double) X_EQUAL+BUTTON_WIDTH*1.5 + 1, (double) y_equal_offset[i]+BUTTON_HEIGHT/2 + 1, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", TEXT_COLOR_BACK, NULL); calcul_line_item[i*2] = goo_canvas_text_new (boardRootItem, BLANK, (double) X_EQUAL+BUTTON_WIDTH*1.5, (double) y_equal_offset[i]+BUTTON_HEIGHT/2, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", TEXT_COLOR_FRONT, NULL); } for (i=0; ilevel-1; i++) { calcul_line_item_back[i*2+1] = goo_canvas_text_new (boardRootItem, BLANK, (double) X_NUM1+BUTTON_WIDTH/2 + 1, (double) y_equal_offset[i+1]+BUTTON_HEIGHT/2 + 1, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", TEXT_COLOR_BACK, NULL); calcul_line_item[i*2+1] = goo_canvas_text_new (boardRootItem, BLANK, (double) X_NUM1+BUTTON_WIDTH/2, (double) y_equal_offset[i+1]+BUTTON_HEIGHT/2, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", TEXT_COLOR_FRONT, NULL); } xOffset = (BOARDWIDTH - 4 * BUTTON_WIDTH - 3 * HORIZONTAL_SEPARATION)/2; for (i=0; i<4; i++) { oper_item[i] = goo_canvas_image_new (boardRootItem, oper_pixmap[i], xOffset , Y_OPE, NULL); xOffset += BUTTON_WIDTH+HORIZONTAL_SEPARATION; g_signal_connect(oper_item[i], "button_press_event", (GCallback) item_event_oper, GINT_TO_POINTER(&(token_value[i*2+1])) ); token_value[i*2+1].isNumber = FALSE; token_value[i*2+1].isMoved = FALSE; token_value[i*2+1].oper = oper_values[i]; } // displays the target result sprintf(str,"%d",result_to_find); result_item_back = goo_canvas_text_new (boardRootItem, str, (double) xOffset+BUTTON_WIDTH +1, (double) Y_OPE+BUTTON_HEIGHT/2 +1, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", TEXT_RESULT_COLOR_BACK, NULL); result_item_front = goo_canvas_text_new (boardRootItem, str, (double) xOffset+BUTTON_WIDTH, (double) Y_OPE+BUTTON_HEIGHT/2, -1, GTK_ANCHOR_CENTER, "font", gc_skin_font_board_title_bold, "fill-color", TEXT_RESULT_COLOR_FRONT, NULL); xOffset = (BOARDWIDTH - (gcomprisBoard->level+1) * BUTTON_WIDTH - gcomprisBoard->level * HORIZONTAL_SEPARATION)/2; for (i=0; ilevel+1; i++) { num_item[i] = goo_canvas_image_new (boardRootItem, num_pixmap[answer_num_index[i]], xOffset , Y_NUM, NULL); sid = g_signal_connect(num_item[i], "button_press_event", (GCallback) item_event_num, (void *)&(token_value[i*2])); token_value[i*2].isNumber = TRUE; token_value[i*2].num = answer_num_index[i]; token_value[i*2].signal_id = sid; token_value[i*2].item = num_item[i]; token_value[i*2].isMoved = FALSE; token_value[i*2].xOffset_original = xOffset; xOffset += BUTTON_WIDTH+HORIZONTAL_SEPARATION; } // items "=" for (i=0; ilevel; i++) { equal_item[i] = goo_canvas_image_new (boardRootItem, oper_pixmap[4], X_EQUAL , y_equal_offset[i], 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; } algebra_guesscount_next_level(); } /* ==================================== */ static gboolean process_time(gpointer data){ if (process_time_id) { g_source_remove (process_time_id); process_time_id = 0; } gc_bonus_display(gamewon, GC_BONUS_RANDOM); return(FALSE); } /* ==================================== */ static int oper_char_to_pixmap_index(char oper) { int i; g_assert(oper == '+' || oper == '-' || oper == 'x' || oper == ':' || oper == '='); for (i=0; i<5; i++) if (oper_values[i] == oper) return i; return -1; } /* ==================================== */ static gboolean item_event_oper (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data) { token *t = ( token *)data; GooCanvasItem * tmp_item; if(board_paused) return FALSE; // first verify it is oper turn if (token_count % 2 == 0 || token_count >= 2*gcomprisBoard->level+1) return FALSE; switch (event->type) { case GDK_BUTTON_PRESS: gc_sound_play_ogg ("sounds/flip.wav", NULL); ptr_token_selected[token_count] = t; tmp_item = goo_canvas_image_new (boardRootItem, oper_pixmap[oper_char_to_pixmap_index(t->oper)], x_token_offset[token_count], y_token_offset[token_count], NULL); token_count++; g_signal_connect(tmp_item, "button_press_event", (GCallback) item_event_oper_moved, GINT_TO_POINTER(token_count)); break; default : break; } return FALSE; } /* ==================================== */ static gboolean item_event_oper_moved (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data) { int count = GPOINTER_TO_INT(data); if(board_paused) return FALSE; // we only allow the undo of an operation if it is the last element displayed switch (event->type) { case GDK_BUTTON_PRESS: gc_sound_play_ogg ("sounds/flip.wav", NULL); if (count == token_count) { goo_canvas_item_remove(item); token_count--; update_line_calcul(); } break; default : break; } return FALSE; } /* ==================================== */ static gboolean item_event_num (GooCanvasItem *item, GooCanvasItem *target, GdkEventButton *event, gpointer data) { token *t = (token *)data; char str[12]; if(board_paused) return FALSE; switch (event->type){ case GDK_BUTTON_PRESS: gc_sound_play_ogg ("sounds/bleep.wav", NULL); if (t->isMoved) { if (item != ptr_token_selected[token_count-1]->item) return FALSE; // we put back in its original place a number item gc_item_absolute_move(item, t->xOffset_original, Y_NUM); token_count--; update_line_calcul(); t->isMoved = FALSE; } else { // the item is at its original place if (token_count % 2 == 1 || token_count > 2*gcomprisBoard->level+1) return FALSE; token_count++; ptr_token_selected[token_count-1] = t; // some operations are illegal A - B must be > 0 and A/B an integer if (token_result() == NO_RESULT && token_count != 1) { token_count--; return FALSE; } gc_item_absolute_move(item, x_token_offset[token_count-1], y_token_offset[token_count-1]); t->isMoved = TRUE; // update result text items if (token_count != 1 && token_count % 2 == 1) { sprintf(str,"%d",token_result()); g_object_set(calcul_line_item[token_count-3], "text", str, NULL); g_object_set(calcul_line_item_back[token_count-3], "text", str, NULL); if(token_count < 2*gcomprisBoard->level+1) { /* No next line to update on last line */ g_object_set(calcul_line_item[token_count-2], "text", str, NULL); g_object_set(calcul_line_item_back[token_count-2], "text", str, NULL); } gamewon = (result_to_find == token_result()); if(gamewon) process_time_id = g_timeout_add (2000, process_time, NULL); } } break; default : break; } return FALSE; } /* ======================================= */ static void destroy_board() { int i; #if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24 for (i=0; i