X-Git-Url: http://vcs.maemo.org/git/?a=blobdiff_plain;f=rfk.c;h=55be231eca03317bc6b35173b54cf9f89a334fc2;hb=9e5935cfc67848bdebe15959244b3e3a584e5175;hp=71079d58d590c378f53bf0a6a8de3dce718ea9d6;hpb=7741c4a02bf90cc09debbc730bc36f160a1c7b31;p=rfk diff --git a/rfk.c b/rfk.c index 71079d5..55be231 100644 --- a/rfk.c +++ b/rfk.c @@ -3,9 +3,10 @@ * ported to maemo by Thomas Thurman, 2009 * suggestions welcome * Compile with: - * gcc -Wall -g rfk.c -o rfk `pkg-config --cflags --libs gtk+-2.0 hildon-1` + * gcc -Wall -g rfk.c -o rfk `pkg-config --cflags --libs gtk+-2.0 hildon-1 dbus-glib-1 dbus-1` */ +#include #include #include #include @@ -19,25 +20,25 @@ const int amount_of_random_stuff = 15; -const char *explanation = - "In this game, you are robot (#). " - "Your job is to find kitten. This task is complicated " - "by the existence of various things which are not kitten. " - "Robot must touch items to determine if they are kitten or " - "not. The game ends when robotfindskitten. You may move " - "robot about by tapping on any side of robot, or with the " - "cursor keys."; +typedef enum { + STATE_PROLOGUE, + STATE_PLAYING, + STATE_EPILOGUE, + STATE_LAST +} StateOfPlay; + +StateOfPlay current_state = STATE_LAST; +GtkWidget* state_widget[STATE_LAST]; GSList *nki = NULL; guint nki_count = 0; GtkWidget *arena[ARENA_WIDTH][ARENA_HEIGHT]; -GtkWidget *table, *window, *robot, *kitten; +GtkWidget *window, *robot, *kitten; int robot_x, robot_y; gboolean *used = NULL; GdkPixbuf *robot_pic, *love_pic, *kitten_pic; -GtkWidget *animation_area; const GdkColor black = { 0, }; @@ -45,7 +46,7 @@ const GdkColor black = { 0, }; /* Random object descriptions. */ /****************************************************************/ -char * +static char * description (void) { int r; @@ -55,8 +56,8 @@ description (void) r = random() % nki_count; } while (used[r]); - used[r] = TRUE; + return g_slist_nth_data (nki, r); } @@ -64,12 +65,12 @@ description (void) /* Placing objects. */ /****************************************************************/ -void +static void place_in_arena_at_xy (GtkWidget *item, int x, int y) { arena[x][y] = item; - gtk_table_attach_defaults (GTK_TABLE (table), + gtk_table_attach_defaults (GTK_TABLE (state_widget[STATE_PLAYING]), item, x, x+1, y, y+1); @@ -81,7 +82,7 @@ place_in_arena_at_xy (GtkWidget *item, int x, int y) } } -void +static void place_in_arena_randomly (GtkWidget *item) { int x, y; @@ -100,7 +101,7 @@ place_in_arena_randomly (GtkWidget *item) /* Labels representing things the robot might find. */ /****************************************************************/ -GtkWidget * +static GtkWidget * random_character (gchar *description) { gchar character[2] = { random() % ('~'-'!') + '!', 0 }; @@ -124,12 +125,13 @@ random_character (gchar *description) /* Talking back to the user. */ /****************************************************************/ -void +static void show_message (const char *message) { HildonNote* note = HILDON_NOTE (hildon_note_new_information (GTK_WINDOW (window), - message)); + message?message: + "Some message was supposed to be here.")); gtk_dialog_run (GTK_DIALOG (note)); gtk_widget_destroy (GTK_WIDGET (note)); } @@ -137,7 +139,8 @@ show_message (const char *message) /****************************************************************/ /* Loading the non-kitten objects. */ /****************************************************************/ -void + +static void ensure_messages_loaded (void) { FILE *nki_file = NULL; @@ -182,81 +185,200 @@ ensure_messages_loaded (void) } fclose (nki_file); +} - used = g_malloc0 (nki_count); +static void +load_images (void) +{ + robot_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-robot.png", NULL); + love_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-love.png", NULL); + kitten_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-kitten.png", NULL); +} + +/****************************************************************/ +/* Stop doing that, and do something else. */ +/****************************************************************/ + +static void +switch_state (StateOfPlay new_state) +{ + if (current_state != STATE_LAST) + { + gtk_container_remove (GTK_CONTAINER (window), state_widget[current_state]); + } + gtk_container_add (GTK_CONTAINER (window), state_widget[new_state]); + + gtk_widget_show_all (window); + gdk_window_set_events (GTK_WIDGET (window)->window, + gdk_window_get_events(GTK_WIDGET (window)->window) | GDK_BUTTON_PRESS_MASK); + + current_state = new_state; +} + +/****************************************************************/ +/* Things we need DBus for: online help, and vibration. */ +/****************************************************************/ + +static void +call_dbus (DBusBusType type, + char *name, + char *path, + char *interface, + char *method, + char *parameter) +{ + DBusGConnection *connection; + GError *error = NULL; + + DBusGProxy *proxy; + + connection = dbus_g_bus_get (type, + &error); + if (connection == NULL) + { + show_message (error->message); + g_error_free (error); + return; + } + + proxy = dbus_g_proxy_new_for_name (connection, name, path, interface); + + error = NULL; + if (!dbus_g_proxy_call (proxy, method, &error, + G_TYPE_STRING, parameter, + G_TYPE_INVALID, + G_TYPE_INVALID)) + { + show_message (error->message); + g_error_free (error); + } +} + +static gboolean +get_help (gpointer button, gpointer data) +{ + call_dbus (DBUS_BUS_SESSION, + "com.nokia.osso_browser", + "/com/nokia/osso_browser/request", + "com.nokia.osso_browser", + "load_url", + "/usr/share/rfk/help.html"); + return FALSE; +} + +static void +vibrate (void) +{ + call_dbus (DBUS_BUS_SYSTEM, + "com.nokia.mce", + "/com/nokia/mce/request", + "com.nokia.mce.request", + "req_vibrator_pattern_activate", + "PatternIncomingMessage"); } /****************************************************************/ /* The ending animation. */ /****************************************************************/ +gboolean animation_running = FALSE; + static gboolean ending_animation_quit (gpointer data) { - gtk_main_quit (); + switch_state (STATE_PROLOGUE); return FALSE; } static gboolean ending_animation_draw (GtkWidget *widget, GdkEventExpose *event, gpointer data) { - /* We only run through once, so just make it static. */ static int cycle_count = 0; static int robot_x = 0; static int robot_stop = 0; static int kitten_x = 0; static int all_y = 0; + static GdkGC *gc = NULL; const int stepsize = 3; + static int love_size = 40; if (!kitten_x) { + gc = gdk_gc_new (GDK_DRAWABLE (widget->window)); + all_y = (event->area.height - gdk_pixbuf_get_height (love_pic)) / 2; robot_stop = gdk_pixbuf_get_width (robot_pic) + gdk_pixbuf_get_width (love_pic); kitten_x = event->area.width - (cycle_count*stepsize + gdk_pixbuf_get_width (kitten_pic)); } - gdk_gc_set_foreground (widget->style->fg_gc[GTK_WIDGET_STATE (widget)], - &black); + gdk_gc_set_foreground (gc, &black); - gdk_draw_rectangle (GDK_DRAWABLE(widget->window), - widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + gdk_draw_rectangle (GDK_DRAWABLE (widget->window), + gc, TRUE, 0, 0, event->area.width, event->area.height); - gdk_draw_pixbuf (GDK_DRAWABLE(widget->window), - widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + gdk_draw_pixbuf (GDK_DRAWABLE (widget->window), + gc, robot_pic, 0, 0, robot_x, all_y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); - gdk_draw_pixbuf (GDK_DRAWABLE(widget->window), - widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + gdk_draw_pixbuf (GDK_DRAWABLE (widget->window), + gc, kitten_pic, 0, 0, kitten_x, all_y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); - cycle_count++; - robot_x += stepsize; - kitten_x -= stepsize; - - if (robot_x+robot_stop >= kitten_x) + if (robot_x+robot_stop < kitten_x) + { + cycle_count++; + robot_x += stepsize; + kitten_x -= stepsize; + } + else { + GdkPixbuf *scaled_love_pic = + gdk_pixbuf_scale_simple (love_pic, + love_size, + love_size, + GDK_INTERP_BILINEAR); + gdk_draw_pixbuf (GDK_DRAWABLE(widget->window), - widget->style->fg_gc[GTK_WIDGET_STATE (widget)], - love_pic, 0, 0, - robot_x + gdk_pixbuf_get_width (robot_pic), all_y, + gc, + scaled_love_pic, 0, 0, + robot_x + gdk_pixbuf_get_width (robot_pic) + + (gdk_pixbuf_get_width (love_pic)-love_size)/2, + all_y + (gdk_pixbuf_get_height (love_pic)-love_size)/2, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); - g_object_unref (love_pic); - love_pic = NULL; + love_size += 10; - g_timeout_add (2000, ending_animation_quit, NULL); + if (love_size >= gdk_pixbuf_get_width (love_pic)) + { + /* all done! */ + + vibrate (); + + animation_running = FALSE; + + g_timeout_add (2000, ending_animation_quit, NULL); + + gdk_gc_unref (gc); + love_size = 40; + cycle_count = 0; + robot_x = 0; + robot_stop = 0; + kitten_x = 0; + all_y = 0; + gc = NULL; + } } return TRUE; @@ -265,9 +387,9 @@ ending_animation_draw (GtkWidget *widget, GdkEventExpose *event, gpointer data) static gboolean ending_animation_step (gpointer data) { - if (love_pic) + if (animation_running) { - gdk_window_invalidate_rect (animation_area->window, + gdk_window_invalidate_rect (state_widget[STATE_EPILOGUE]->window, NULL, TRUE); return TRUE; @@ -279,18 +401,11 @@ ending_animation_step (gpointer data) static void ending_animation () { - robot_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-robot.png", NULL); - love_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-love.png", NULL); - kitten_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-kitten.png", NULL); - animation_area = gtk_drawing_area_new (); - - gtk_container_remove (GTK_CONTAINER (window), GTK_WIDGET (table)); - gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (animation_area)); - gtk_widget_show_all (window); - - g_signal_connect (G_OBJECT (animation_area), - "expose_event", G_CALLBACK (ending_animation_draw), NULL); - g_timeout_add (10, ending_animation_step, NULL); + if (current_state!=STATE_EPILOGUE) + { + animation_running = TRUE; + g_timeout_add (10, ending_animation_step, NULL); + } } /****************************************************************/ @@ -315,7 +430,7 @@ direction directions[] = { { GDK_Up, 'k', 0, -1 } }; -gboolean +static gboolean move_robot (guint8 whichway) { GtkWidget *new_space; @@ -339,7 +454,7 @@ move_robot (guint8 whichway) if (new_space == kitten) { - ending_animation (); + switch_state (STATE_EPILOGUE); } return TRUE; @@ -350,8 +465,8 @@ move_robot (guint8 whichway) g_object_ref (new_space); - gtk_container_remove (GTK_CONTAINER (table), robot); - gtk_container_remove (GTK_CONTAINER (table), new_space); + gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]), robot); + gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]), new_space); place_in_arena_at_xy (new_space, robot_x, robot_y); place_in_arena_at_xy (robot, robot_x+dx, robot_y+dy); @@ -366,7 +481,7 @@ move_robot (guint8 whichway) /* Event handlers. */ /****************************************************************/ -gboolean +static gboolean on_window_clicked (GtkWidget *widget, GdkEventButton *event, gpointer user_data) @@ -375,6 +490,11 @@ on_window_clicked (GtkWidget *widget, int rx, ry; double angle; + if (current_state!=STATE_PLAYING) + { + return TRUE; + } + rx = (robot->allocation.x+robot->allocation.width/2); ry = (robot->allocation.y+robot->allocation.height/2); @@ -387,7 +507,7 @@ on_window_clicked (GtkWidget *widget, return TRUE; } -gboolean +static gboolean on_key_pressed (GtkWidget *widget, GdkEventKey *event, gpointer user_data) @@ -395,6 +515,11 @@ on_key_pressed (GtkWidget *widget, gint i; guint keyval = event->keyval; + if (current_state!=STATE_PLAYING) + { + return FALSE; + } + if (keyval>='A' && keyval<='Z') { keyval += ('a'-'A'); @@ -420,65 +545,214 @@ on_key_pressed (GtkWidget *widget, } } + if (keyval=='d' && event->state & GDK_CONTROL_MASK) + { + /* secret debugging key */ + show_message (gtk_label_get_text (GTK_LABEL (kitten))); + } + return FALSE; } -/****************************************************************/ -/* Let's kick the whole thing off... */ -/****************************************************************/ +static void +play_game (gpointer button, gpointer data) +{ + switch_state (STATE_PLAYING); +} -int -main (gint argc, - gchar **argv) +static void +restart (gpointer button, gpointer data) { - int x, y; + if (current_state == STATE_EPILOGUE) + { + show_message ("Have patience while robotfindskitten."); + } + else + { + switch_state (STATE_PROLOGUE); + } +} - gtk_init (&argc, &argv); - g_set_application_name ("robotfindskitten"); - srandom (time(0)); +static void +set_up_board (void) +{ + guint x, y; - ensure_messages_loaded (); + if (current_state==STATE_PLAYING) + { + /* end of the game; clean up */ + + for (x=0; x < ARENA_WIDTH; x++) + for (y=0; y < ARENA_HEIGHT; y++) + if (arena[x][y]) + { + gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]), + arena[x][y]); + arena[x][y] = NULL; + } + + g_object_unref (robot); + g_object_unref (kitten); + } + else + { + /* make everything new */ + + g_free (used); + used = g_malloc0 (nki_count * sizeof(gboolean)); + + robot = gtk_label_new ("#"); + g_object_ref (robot); + kitten = random_character ("You found kitten! Way to go, robot!"); + g_object_ref (kitten); + + place_in_arena_randomly (robot); + place_in_arena_randomly (kitten); - window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + if (nki_count < amount_of_random_stuff) + { + /* sanity check failed */ + show_message ("There are too few non-kitten items to play a meaningful game."); + exit (EXIT_FAILURE); + } + + for (x=0; x < amount_of_random_stuff; x++) + place_in_arena_randomly (random_character (description ())); + + for (x=0; x < ARENA_WIDTH; x++) + for (y=0; y < ARENA_HEIGHT; y++) + if (!arena[x][y]) + place_in_arena_at_xy (gtk_label_new (NULL), x, y); + } +} + +static void +set_up_widgets (void) +{ + GtkWidget *middle = gtk_hbox_new (FALSE, 0); + GtkWidget *buttons = gtk_hbox_new (TRUE, 0); + GtkWidget *explain = NULL, *button, *intro; + const char *explanation = + "In this game, you are robot (#). " + "Your job is to find kitten. This task is complicated " + "by the existence of various things which are not kitten. " + "Robot must touch items to determine if they are kitten or " + "not. The game ends when robotfindskitten. You may move " + "robot about by tapping on any side of robot, or with the " + "arrow keys."; + GKeyFile *desktop = g_key_file_new (); + gchar *version; + guint x, y; + HildonAppMenu *menu = HILDON_APP_MENU (hildon_app_menu_new ()); + + /* The window */ + + window = hildon_window_new (); gtk_window_set_title (GTK_WINDOW (window), "robotfindskitten"); gtk_widget_modify_bg (window, GTK_STATE_NORMAL, &black); g_signal_connect (G_OBJECT (window), "button-press-event", G_CALLBACK (on_window_clicked), NULL); g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (on_key_pressed), NULL); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL); - - table = gtk_table_new (ARENA_HEIGHT, ARENA_WIDTH, TRUE); - gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (table)); - - robot = gtk_label_new ("#"); - g_object_ref (robot); - kitten = random_character ("You found kitten! Way to go, robot!"); - place_in_arena_randomly (robot); - place_in_arena_randomly (kitten); + /* The prologue */ - if (nki_count < amount_of_random_stuff) + /* Get the rather odd version string. The RFK spec says that + * it should read v... + */ + if (g_key_file_load_from_file (desktop, + "/usr/share/applications/hildon/rfk.desktop", + G_KEY_FILE_NONE, + NULL)) { - gtk_widget_show_all (window); - show_message ("There are too few non-kitten items to play a meaningful game."); - exit (EXIT_FAILURE); + version = g_strdup_printf("v%s.%d", + g_key_file_get_value (desktop, "Desktop Entry", "Version", NULL), + nki_count); + g_key_file_free (desktop); } + else + { + version = g_strdup(""); + } + + button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_HORIZONTAL, + "Play", NULL); + g_signal_connect (button, "clicked", G_CALLBACK (play_game), NULL); + + gtk_box_pack_end (GTK_BOX (buttons), button, TRUE, TRUE, 0); + + button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_HORIZONTAL, + "Help", NULL); + g_signal_connect (button, "clicked", G_CALLBACK (get_help), NULL); + gtk_box_pack_end (GTK_BOX (buttons), button, TRUE, TRUE, 0); + + /* and another help button, this time for the menu */ + button = gtk_button_new_with_label ("Help"); + g_signal_connect (button, "clicked", G_CALLBACK (get_help), NULL); + hildon_app_menu_append (menu, GTK_BUTTON (button)); + + button = gtk_button_new_with_label ("Restart"); + g_signal_connect (button, "clicked", G_CALLBACK (restart), NULL); + hildon_app_menu_append (menu, GTK_BUTTON (button)); + + gtk_widget_show_all (GTK_WIDGET (menu)); + hildon_window_set_app_menu (HILDON_WINDOW (window), menu); + + explain = gtk_label_new (explanation); + gtk_label_set_line_wrap (GTK_LABEL (explain), TRUE); + + gtk_box_pack_end (GTK_BOX (middle), explain, TRUE, TRUE, 0); + gtk_box_pack_end (GTK_BOX (middle), gtk_image_new_from_pixbuf (robot_pic), FALSE, FALSE, 0); + + intro = gtk_vbox_new (FALSE, 0); + gtk_box_pack_end (GTK_BOX (intro), buttons, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (intro), middle, TRUE, TRUE, 0); + gtk_box_pack_end (GTK_BOX (intro), gtk_label_new (version), FALSE, FALSE, 0); + g_free (version); + + state_widget[STATE_PROLOGUE] = intro; + + /* The game itself */ - for (x=0; x < amount_of_random_stuff; x++) - place_in_arena_randomly (random_character (description ())); + state_widget[STATE_PLAYING] = gtk_table_new (ARENA_HEIGHT, ARENA_WIDTH, TRUE); + g_signal_connect (state_widget[STATE_PLAYING], "parent-set", G_CALLBACK (set_up_board), NULL); for (x=0; x < ARENA_WIDTH; x++) for (y=0; y < ARENA_HEIGHT; y++) - if (!arena[x][y]) - place_in_arena_at_xy (gtk_label_new (NULL), x, y); + arena[x][y] = NULL; - gtk_widget_show_all (window); + /* The epilogue */ + state_widget[STATE_EPILOGUE] = gtk_drawing_area_new (); + g_signal_connect (state_widget[STATE_EPILOGUE], "parent-set", G_CALLBACK (ending_animation), NULL); + g_signal_connect (G_OBJECT (state_widget[STATE_EPILOGUE]), + "expose_event", G_CALLBACK (ending_animation_draw), NULL); - gdk_window_set_events (GTK_WIDGET (window)->window, - gdk_window_get_events(GTK_WIDGET (window)->window) | GDK_BUTTON_PRESS_MASK); + for (x=0; x