/* gcompris - clockgame.c
*
* Copyright (C) 2000, 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
#include
#include "gcompris/gcompris.h"
#define SOUNDLISTFILE PACKAGE
static GcomprisBoard *gcomprisBoard = NULL;
static gboolean board_paused = TRUE;
static GooCanvasItem *boardRootItem = NULL;
static GooCanvasItem *second_item;
static GooCanvasItem *hour_item;
static GooCanvasItem *minute_item;
static GooCanvasItem *digital_time_item;
static GooCanvasItem *time_to_find_item;
static gboolean dragging = FALSE;
static double drag_x, drag_y;
/* Center of the clock and it's size */
static double cx;
static double cy;
static double clock_size;
typedef struct {
guint hour;
guint minute;
guint second;
} GcomprisTime;
static GcomprisTime timeToFind, currentTime;
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 void clockgame_create_item(GooCanvasItem *parent);
static void destroy_all_items(void);
static void get_random_hour(GcomprisTime *time);
static void clockgame_next_level(void);
static gboolean on_button_release (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data);
static gboolean on_button_press (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data);
static gboolean on_motion_notify (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventMotion *event,
gpointer data);
static void display_hour(guint hour);
static void display_minute(guint minute);
static void display_second(guint second);
static gboolean time_equal(GcomprisTime *time1, GcomprisTime *time2);
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Learning Clock",
"Learn how to tell the time",
"Bruno Coudoin ",
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(clockgame)
/*
* 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();
}
board_paused = pause;
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
"clockgame/clockgame-bg0.svgz");
/* set initial values for this level adjusted to
fit the watch background */
cx = BOARDWIDTH/2 + 16;
cy = BOARDHEIGHT*0.4 + 42;
clock_size = BOARDHEIGHT*0.3;
gcomprisBoard->level=1;
gcomprisBoard->maxlevel=7;
gcomprisBoard->sublevel=1;
gcomprisBoard->number_of_sublevel=3; /* Go to next level after this number of 'play' */
gc_score_start(SCORESTYLE_NOTE,
BOARDWIDTH - 195,
30,
gcomprisBoard->number_of_sublevel);
gc_bar_set(GC_BAR_LEVEL);
gc_bar_location(BOARDWIDTH - 200, -1, 0.8);
clockgame_next_level();
gamewon = FALSE;
pause_board(FALSE);
}
}
static void
end_board ()
{
if(gcomprisBoard!=NULL)
{
pause_board(TRUE);
gc_score_end();
destroy_all_items();
}
gcomprisBoard = NULL;
}
static void
set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
gcomprisBoard->sublevel=1;
clockgame_next_level();
}
}
static gboolean
is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_ascii_strcasecmp(gcomprisBoard->type, "clockgame")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void clockgame_next_level()
{
gc_bar_set_level(gcomprisBoard);
gc_score_set(gcomprisBoard->sublevel);
destroy_all_items();
/* Try the next level */
get_random_hour(&timeToFind);
/* Avoid to show up the solution directly */
do {
get_random_hour(¤tTime);
} while(time_equal(&timeToFind, ¤tTime));
clockgame_create_item(goo_canvas_get_root_item(gcomprisBoard->canvas));
}
/* ==================================== */
/* Destroy all the items */
static void
destroy_all_items()
{
if(boardRootItem!=NULL)
goo_canvas_item_remove(boardRootItem);
boardRootItem = NULL;
}
static void display_digital_time(GooCanvasItem *item, GcomprisTime *time)
{
gchar *text = NULL;
int temps;
temps = (time->hour+12)*3600 + time->minute*60 + time->second;
time->hour = (temps / 3600) % 12;
time->minute = (temps / 60) % 60;
time->second = temps % 60;
if(item==NULL)
return;
if(gcomprisBoard->level<=2)
text = g_strdup_printf("%.2d:%.2d", time->hour, time->minute);
else
text = g_strdup_printf("%.2d:%.2d:%.2d", time->hour, time->minute, time->second);
g_object_set (item,
"text", text,
NULL);
g_free(text);
}
static void display_hour(guint hour)
{
double needle_size = clock_size*0.70;
double ang;
GooCanvasPoints *canvasPoints;
if(hour_item==NULL)
return;
/* Calc the needle angle */
ang = ((hour > 12) ? hour-12 : hour) * M_PI / 6;
ang += currentTime.minute * M_PI / 360;
ang += currentTime.second * M_PI / 21600;
canvasPoints = goo_canvas_points_new (2);
canvasPoints->coords[0]=cx;
canvasPoints->coords[1]=cy;
canvasPoints->coords[2]=cx + needle_size * sin(ang);
canvasPoints->coords[3]=cy - needle_size * cos(ang);
double w = 4.0;
g_object_set (hour_item,
"points", canvasPoints,
"stroke-color", "darkblue",
"line-width", w,
"end-arrow", TRUE,
"arrow-tip-length", needle_size/w,
"arrow-length", 4.0,
"arrow-width", 4.0,
NULL);
goo_canvas_points_unref(canvasPoints);
currentTime.hour=hour;
display_digital_time(digital_time_item, ¤tTime);
}
static void display_minute(guint minute)
{
double needle_size = clock_size;
double ang;
GooCanvasPoints *canvasPoints;
if(minute_item==NULL)
return;
ang = minute * M_PI / 30;
ang += currentTime.second * M_PI / 1800;
canvasPoints = goo_canvas_points_new (2);
canvasPoints->coords[0]=cx;
canvasPoints->coords[1]=cy;
canvasPoints->coords[2]=cx + needle_size * sin(ang);
canvasPoints->coords[3]=cy - needle_size * cos(ang);
double w = 4.0;
g_object_set (minute_item,
"points", canvasPoints,
"stroke-color", "red",
"line-width", w,
"end-arrow", TRUE,
"arrow-tip-length", needle_size/w,
"arrow-length", (double) 4.0,
"arrow-width", (double) 3.0,
NULL);
goo_canvas_points_unref(canvasPoints);
currentTime.minute=minute;
display_digital_time(digital_time_item, ¤tTime);
}
static void display_second(guint second)
{
double needle_size = clock_size;
double ang;
GooCanvasPoints *canvasPoints;
/* No seconds at first levels */
if(second_item==NULL || gcomprisBoard->level<=2)
return;
ang = second * M_PI / 30;
canvasPoints = goo_canvas_points_new (2);
canvasPoints->coords[0]=cx;
canvasPoints->coords[1]=cy;
canvasPoints->coords[2]=cx + needle_size * sin(ang);
canvasPoints->coords[3]=cy - needle_size * cos(ang);
g_object_set (second_item,
"points", canvasPoints,
"stroke-color-rgba", 0x68c46fFF,
"line-width", 4.0,
NULL);
goo_canvas_points_unref(canvasPoints);
currentTime.second=second;
display_digital_time(digital_time_item, ¤tTime);
}
static void
clockgame_create_item(GooCanvasItem *parent)
{
double needle_size = clock_size;
double min_point_size = clock_size*0.05;
double hour_point_size = clock_size*0.1;
double ang;
guint min;
GooCanvasPoints *canvasPoints;
char *color;
char *color_text;
gchar *mtext = NULL;
gchar *font = NULL;
if(gcomprisBoard->level > 4)
{
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
"clockgame/clockgame-bg.svgz");
}
else
{
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
"clockgame/clockgame-bg0.svgz");
}
boardRootItem = goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas),
NULL);
canvasPoints = goo_canvas_points_new (2);
for(min = 1 ; min <= 60 ; min += 1)
{
ang = min * M_PI / 30;
if(min%5==0)
{
/* Hour point */
canvasPoints->coords[0]=cx + (needle_size-hour_point_size) * sin(ang);
canvasPoints->coords[1]=cy - (needle_size-hour_point_size) * cos(ang);
color="darkblue";
color_text="red";
font = gc_skin_font_board_small;
}
else
{
/* Minute point */
canvasPoints->coords[0]=cx + (needle_size-min_point_size) * sin(ang);
canvasPoints->coords[1]=cy - (needle_size-min_point_size) * cos(ang);
color="red";
color_text="red";
font = gc_skin_font_board_tiny;
}
canvasPoints->coords[2]=cx + needle_size * sin(ang);
canvasPoints->coords[3]=cy - needle_size * cos(ang);
goo_canvas_polyline_new (boardRootItem, FALSE, 2,
canvasPoints->coords[0],
canvasPoints->coords[1],
canvasPoints->coords[2],
canvasPoints->coords[3],
"stroke-color", color,
"line-width", (double)1,
NULL);
/* Display minute number */
if(gcomprisBoard->level<6)
{
mtext = g_strdup_printf("%d", min);
goo_canvas_text_new (boardRootItem,
mtext,
(double) cx + (needle_size+10) * sin(ang),
(double) cy - (needle_size+10) * cos(ang),
-1,
GTK_ANCHOR_CENTER,
"font", font,
"fill-color", color_text,
NULL);
g_free(mtext);
}
/* Display hour numbers */
if(gcomprisBoard->level<7)
if(min%5==0)
{
mtext = g_strdup_printf( "%d", min/5);
goo_canvas_text_new (boardRootItem,
mtext,
(double) cx + (needle_size-30) * sin(ang),
(double) cy - (needle_size-30) * cos(ang),
-1,
GTK_ANCHOR_CENTER,
"font", font,
"fill-color", "blue",
NULL);
g_free(mtext);
}
}
/* Create the text area for the digital time display */
if(gcomprisBoard->level<4)
{
digital_time_item =
goo_canvas_text_new (boardRootItem,
"",
(double) cx,
(double) cy + needle_size/2,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_medium,
"fill-color", "black",
NULL);
display_digital_time(digital_time_item, ¤tTime);
}
else
{
digital_time_item = NULL;
}
/* Create the Hour needle */
hour_item = goo_canvas_polyline_new (boardRootItem, FALSE, 0,
NULL);
g_signal_connect (hour_item, "motion_notify_event",
(GCallback) on_motion_notify, NULL);
g_signal_connect (hour_item, "button_press_event",
(GCallback) on_button_press, NULL);
g_signal_connect (hour_item, "button_release_event",
(GCallback) on_button_release, NULL);
display_hour(currentTime.hour);
/* Create the minute needle */
minute_item = goo_canvas_polyline_new (boardRootItem, FALSE, 0,
NULL);
g_signal_connect (minute_item, "motion_notify_event",
(GCallback) on_motion_notify, NULL);
g_signal_connect (minute_item, "button_press_event",
(GCallback) on_button_press, NULL);
g_signal_connect (minute_item, "button_release_event",
(GCallback) on_button_release, NULL);
display_minute(currentTime.minute);
/* Create the second needle */
second_item = goo_canvas_polyline_new (boardRootItem, FALSE, 0,
NULL);
g_signal_connect (second_item, "motion_notify_event",
(GCallback) on_motion_notify, NULL);
g_signal_connect (second_item, "button_press_event",
(GCallback) on_button_press, NULL);
g_signal_connect (second_item, "button_release_event",
(GCallback) on_button_release, NULL);
display_second(currentTime.second);
/* Create the text area for the time to find display */
goo_canvas_text_new (boardRootItem,
_("Set the watch to:"),
(double) BOARDWIDTH*0.17,
(double) cy + needle_size + needle_size / 3 - 30,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_small,
"fill-color", "black",
NULL);
time_to_find_item = \
goo_canvas_text_new (boardRootItem,
"",
(double) BOARDWIDTH*0.17,
(double) cy + needle_size + needle_size / 3,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_big_bold,
"fill-color", "black",
NULL);
display_digital_time(time_to_find_item, &timeToFind);
goo_canvas_points_unref(canvasPoints);
}
/*
* Returns true is given times are equal
*/
static gboolean
time_equal(GcomprisTime *time1, GcomprisTime *time2)
{
/* No seconds at first levels */
if(second_item==NULL || gcomprisBoard->level<=2)
return(time1->hour==time2->hour
&&time1->minute==time2->minute);
else
return(time1->hour==time2->hour
&&time1->minute==time2->minute
&&time1->second==time2->second);
}
/* ==================================== */
static void game_won()
{
gamewon = FALSE;
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);
}
clockgame_next_level();
}
static gboolean
on_button_press (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data)
{
GooCanvas *canvas;
GdkCursor *fleur;
canvas = goo_canvas_item_get_canvas (item);
switch (event->button)
{
case 1:
{
gc_sound_play_ogg ("sounds/bleep.wav", NULL);
goo_canvas_item_raise(item, NULL);
drag_x = event->x;
drag_y = event->y;
fleur = gdk_cursor_new (GDK_FLEUR);
goo_canvas_pointer_grab (canvas, item,
GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
fleur,
event->time);
gdk_cursor_unref (fleur);
dragging = TRUE;
}
break;
default:
break;
}
return TRUE;
}
static gboolean
on_button_release (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data)
{
GooCanvas *canvas;
canvas = goo_canvas_item_get_canvas (item);
goo_canvas_pointer_ungrab (canvas, item, event->time);
dragging = FALSE;
if(time_equal(&timeToFind, ¤tTime))
{
gamewon = TRUE;
gc_bonus_display(gamewon, GC_BONUS_FLOWER);
}
return TRUE;
}
static gboolean
on_motion_notify (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventMotion *event,
gpointer data)
{
if (dragging && (event->state & GDK_BUTTON1_MASK))
{
double angle;
double item_x, item_y;
double new_x, new_y;
item_x = event->x;
item_y = event->y;
goo_canvas_convert_from_item_space(goo_canvas_item_get_canvas(item),
item, &item_x, &item_y);
/* only use coords relative to the center, with standard
direction for axis (y's need to be negated for this) */
new_x = item_x - cx;
new_y = - item_y + cy;
/* angle as mesured relatively to noon position */
/* Thanks to Martin Herweg for this code */
angle = atan2(new_x,new_y);
if (angle<0) {angle= angle + 2.0*M_PI;}
if(item==hour_item)
display_hour(angle * 6 / M_PI);
else if(item==minute_item)
{
if(currentTime.minute > 45 && angle * 30 / M_PI < 15)
{
currentTime.hour++;
gc_sound_play_ogg ("sounds/paint1.wav", NULL);
}
if(currentTime.minute < 15 && angle * 30 / M_PI > 45)
{
currentTime.hour--;
gc_sound_play_ogg ("sounds/paint1.wav", NULL);
}
display_minute(angle * 30 / M_PI);
display_hour(currentTime.hour);
}
else if(item==second_item)
{
if(currentTime.second > 45 && angle * 30 / M_PI < 15)
{
currentTime.minute++;
gc_sound_play_ogg ("sounds/paint1.wav", NULL);
}
if(currentTime.second < 15 && angle * 30 / M_PI > 45)
{
currentTime.minute--;
gc_sound_play_ogg ("sounds/paint1.wav", NULL);
}
display_second(angle * 30 / M_PI);
display_minute(currentTime.minute);
display_hour(currentTime.hour);
}
}
return TRUE;
}
/* Returns a random time based on the current level */
static void get_random_hour(GcomprisTime *time)
{
time->hour=g_random_int()%12;
if(gcomprisBoard->level>3)
time->second=g_random_int()%60;
else time->second=0;
time->minute=g_random_int()%60;
switch(gcomprisBoard->level)
{
case 1:
time->minute=g_random_int()%4*15;
break;
case 2:
time->minute=g_random_int()%12*5;
break;
default:
break;
}
}