1 /* robotfindskitten for maemo
2 * original by Leonard Richardson, 1997
3 * ported to maemo by Thomas Thurman, 2009
6 * gcc -Wall -g rfk.c -o rfk `pkg-config --cflags --libs gtk+-2.0 hildon-1 dbus-glib-1 dbus-1`
9 #include <dbus/dbus-glib.h>
13 #include <hildon/hildon.h>
18 #define ARENA_WIDTH 25
19 #define ARENA_HEIGHT 12
21 const int amount_of_random_stuff = 15;
30 StateOfPlay current_state = STATE_LAST;
31 GtkWidget* state_widget[STATE_LAST];
36 GtkWidget *arena[ARENA_WIDTH][ARENA_HEIGHT];
37 GtkWidget *window, *robot, *kitten;
39 gboolean *used = NULL;
41 GdkPixbuf *robot_pic, *love_pic, *kitten_pic;
43 const GdkColor black = { 0, };
45 /****************************************************************/
46 /* Random object descriptions. */
47 /****************************************************************/
56 r = random() % nki_count;
61 return g_slist_nth_data (nki, r);
64 /****************************************************************/
65 /* Placing objects. */
66 /****************************************************************/
69 place_in_arena_at_xy (GtkWidget *item, int x, int y)
73 gtk_table_attach_defaults (GTK_TABLE (state_widget[STATE_PLAYING]),
86 place_in_arena_randomly (GtkWidget *item)
92 x = random() % ARENA_WIDTH;
93 y = random() % ARENA_HEIGHT;
97 place_in_arena_at_xy (item, x, y);
100 /****************************************************************/
101 /* Labels representing things the robot might find. */
102 /****************************************************************/
105 random_character (gchar *description)
107 gchar character[2] = { random() % ('~'-'!') + '!', 0 };
108 gchar *escaped_character = g_markup_escape_text (character, -1);
109 gchar *markup = g_strdup_printf ("<span color=\"#%02x%02x%02x\">%s</span>",
110 (int) (random() % 0x7F)+0x80,
111 (int) (random() % 0x7F)+0x80,
112 (int) (random() % 0x7F)+0x80,
114 GtkWidget *result = gtk_label_new (NULL);
115 gtk_label_set_markup (GTK_LABEL (result), markup);
117 g_free (escaped_character);
119 g_object_set_data (G_OBJECT (result), "examine", description);
124 /****************************************************************/
125 /* Talking back to the user. */
126 /****************************************************************/
129 show_message (const char *message)
131 HildonNote* note = HILDON_NOTE
132 (hildon_note_new_information (GTK_WINDOW (window),
134 "Some message was supposed to be here."));
135 gtk_dialog_run (GTK_DIALOG (note));
136 gtk_widget_destroy (GTK_WIDGET (note));
139 /****************************************************************/
140 /* Loading the non-kitten objects. */
141 /****************************************************************/
144 ensure_messages_loaded (void)
146 FILE *nki_file = NULL;
148 gboolean headers = TRUE;
153 nki_file = fopen ("/usr/share/rfk/non-kitten-items.rfk", "r");
157 show_message ("Could not open list of non-kitten items! Must quit.");
161 while (!feof (nki_file))
164 if (fscanf (nki_file, "%a[^\n]%c", &line, &newline) == EOF)
169 if (strcmp(line, "")==0)
172 fscanf (nki_file, "%c", &newline);
177 /* we ignore all the headers for now */
182 nki = g_slist_prepend (nki, line);
193 robot_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-robot.png", NULL);
194 love_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-love.png", NULL);
195 kitten_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-kitten.png", NULL);
198 /****************************************************************/
199 /* Stop doing that, and do something else. */
200 /****************************************************************/
203 switch_state (StateOfPlay new_state)
205 if (current_state != STATE_LAST)
207 gtk_container_remove (GTK_CONTAINER (window), state_widget[current_state]);
209 gtk_container_add (GTK_CONTAINER (window), state_widget[new_state]);
211 gtk_widget_show_all (window);
212 gdk_window_set_events (GTK_WIDGET (window)->window,
213 gdk_window_get_events(GTK_WIDGET (window)->window) | GDK_BUTTON_PRESS_MASK);
215 current_state = new_state;
218 /****************************************************************/
219 /* Things we need DBus for: online help, and vibration. */
220 /****************************************************************/
223 call_dbus (DBusBusType type,
230 DBusGConnection *connection;
231 GError *error = NULL;
235 connection = dbus_g_bus_get (type,
237 if (connection == NULL)
239 show_message (error->message);
240 g_error_free (error);
244 proxy = dbus_g_proxy_new_for_name (connection, name, path, interface);
247 if (!dbus_g_proxy_call (proxy, method, &error,
248 G_TYPE_STRING, parameter,
252 show_message (error->message);
253 g_error_free (error);
258 get_help (gpointer button, gpointer data)
260 call_dbus (DBUS_BUS_SESSION,
261 "com.nokia.osso_browser",
262 "/com/nokia/osso_browser/request",
263 "com.nokia.osso_browser",
265 "/usr/share/rfk/help.html");
272 call_dbus (DBUS_BUS_SYSTEM,
274 "/com/nokia/mce/request",
275 "com.nokia.mce.request",
276 "req_vibrator_pattern_activate",
277 "PatternIncomingMessage");
280 /****************************************************************/
281 /* The ending animation. */
282 /****************************************************************/
284 gboolean animation_running = FALSE;
287 ending_animation_quit (gpointer data)
289 switch_state (STATE_PROLOGUE);
294 ending_animation_draw (GtkWidget *widget, GdkEventExpose *event, gpointer data)
296 static int cycle_count = 0;
298 static int robot_x = 0;
299 static int robot_stop = 0;
300 static int kitten_x = 0;
301 static int all_y = 0;
302 static GdkGC *gc = NULL;
304 const int stepsize = 3;
305 static int love_size = 40;
309 gc = gdk_gc_new (GDK_DRAWABLE (widget->window));
311 all_y = (event->area.height - gdk_pixbuf_get_height (love_pic)) / 2;
313 robot_stop = gdk_pixbuf_get_width (robot_pic) + gdk_pixbuf_get_width (love_pic);
314 kitten_x = event->area.width - (cycle_count*stepsize + gdk_pixbuf_get_width (kitten_pic));
317 gdk_gc_set_foreground (gc, &black);
319 gdk_draw_rectangle (GDK_DRAWABLE (widget->window),
322 0, 0, event->area.width, event->area.height);
324 gdk_draw_pixbuf (GDK_DRAWABLE (widget->window),
329 GDK_RGB_DITHER_NONE, 0, 0);
331 gdk_draw_pixbuf (GDK_DRAWABLE (widget->window),
336 GDK_RGB_DITHER_NONE, 0, 0);
338 if (robot_x+robot_stop < kitten_x)
342 kitten_x -= stepsize;
346 GdkPixbuf *scaled_love_pic =
347 gdk_pixbuf_scale_simple (love_pic,
350 GDK_INTERP_BILINEAR);
352 gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
354 scaled_love_pic, 0, 0,
355 robot_x + gdk_pixbuf_get_width (robot_pic) +
356 (gdk_pixbuf_get_width (love_pic)-love_size)/2,
357 all_y + (gdk_pixbuf_get_height (love_pic)-love_size)/2,
359 GDK_RGB_DITHER_NONE, 0, 0);
363 if (love_size >= gdk_pixbuf_get_width (love_pic))
369 animation_running = FALSE;
371 g_timeout_add (2000, ending_animation_quit, NULL);
388 ending_animation_step (gpointer data)
390 if (animation_running)
392 gdk_window_invalidate_rect (state_widget[STATE_EPILOGUE]->window,
404 if (current_state!=STATE_EPILOGUE)
406 animation_running = TRUE;
407 g_timeout_add (10, ending_animation_step, NULL);
411 /****************************************************************/
412 /* Moving the robot. Way to go, robot! */
413 /****************************************************************/
417 gchar vi_key; /* or nethack equivalent */
422 direction directions[] = {
423 { GDK_Home, 'y', -1, -1 },
424 { GDK_Left, 'h', -1, 0 },
425 { GDK_End, 'b', -1, 1 },
426 { GDK_Down, 'j', 0, 1 },
427 { GDK_Page_Down, 'n', 1, 1 },
428 { GDK_Right, 'l', 1, 0 },
429 { GDK_Page_Up, 'u', 1, -1 },
430 { GDK_Up, 'k', 0, -1 }
434 move_robot (guint8 whichway)
436 GtkWidget *new_space;
437 gint8 dx = directions[whichway].move_x;
438 gint8 dy = directions[whichway].move_y;
444 robot_x+dx>=ARENA_WIDTH ||
445 robot_y+dy>=ARENA_HEIGHT)
448 new_space = arena[robot_x+dx][robot_y+dy];
449 found = g_object_get_data (G_OBJECT (new_space), "examine");
453 show_message (found);
455 if (new_space == kitten)
457 switch_state (STATE_EPILOGUE);
464 /* just an ordinary move into an empty space */
466 g_object_ref (new_space);
468 gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]), robot);
469 gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]), new_space);
471 place_in_arena_at_xy (new_space, robot_x, robot_y);
472 place_in_arena_at_xy (robot, robot_x+dx, robot_y+dy);
474 g_object_unref (new_space);
480 /****************************************************************/
481 /* Event handlers. */
482 /****************************************************************/
485 on_window_clicked (GtkWidget *widget,
486 GdkEventButton *event,
489 /** Centre point of robot's representation on screen */
493 if (current_state!=STATE_PLAYING)
498 rx = (robot->allocation.x+robot->allocation.width/2);
499 ry = (robot->allocation.y+robot->allocation.height/2);
501 angle = atan2(event->x - rx,
505 move_robot (((int) (angle / (M_PI/4))) % 8);
511 on_key_pressed (GtkWidget *widget,
516 guint keyval = event->keyval;
518 if (current_state!=STATE_PLAYING)
523 if (keyval>='A' && keyval<='Z')
528 for (i=0; i<G_N_ELEMENTS(directions); i++)
530 if (keyval==directions[i].gdk_key ||
531 keyval==directions[i].vi_key)
533 if (event->state & GDK_SHIFT_MASK)
535 while (!move_robot (i))
537 /* keep going, robot! */
548 if (keyval=='d' && event->state & GDK_CONTROL_MASK)
550 /* secret debugging key */
551 show_message (gtk_label_get_text (GTK_LABEL (kitten)));
558 play_game (gpointer button, gpointer data)
560 switch_state (STATE_PLAYING);
564 restart (gpointer button, gpointer data)
566 if (current_state == STATE_EPILOGUE)
568 show_message ("Have patience while robotfindskitten.");
572 switch_state (STATE_PROLOGUE);
581 if (current_state==STATE_PLAYING)
583 /* end of the game; clean up */
585 for (x=0; x < ARENA_WIDTH; x++)
586 for (y=0; y < ARENA_HEIGHT; y++)
589 gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]),
594 g_object_unref (robot);
595 g_object_unref (kitten);
599 /* make everything new */
602 used = g_malloc0 (nki_count * sizeof(gboolean));
604 robot = gtk_label_new ("#");
605 g_object_ref (robot);
606 kitten = random_character ("You found kitten! Way to go, robot!");
607 g_object_ref (kitten);
609 place_in_arena_randomly (robot);
610 place_in_arena_randomly (kitten);
612 if (nki_count < amount_of_random_stuff)
614 /* sanity check failed */
615 show_message ("There are too few non-kitten items to play a meaningful game.");
619 for (x=0; x < amount_of_random_stuff; x++)
620 place_in_arena_randomly (random_character (description ()));
622 for (x=0; x < ARENA_WIDTH; x++)
623 for (y=0; y < ARENA_HEIGHT; y++)
625 place_in_arena_at_xy (gtk_label_new (NULL), x, y);
630 set_up_widgets (void)
632 GtkWidget *middle = gtk_hbox_new (FALSE, 0);
633 GtkWidget *buttons = gtk_hbox_new (TRUE, 0);
634 GtkWidget *explain = NULL, *button, *intro;
635 const char *explanation =
636 "In this game, you are robot (#). "
637 "Your job is to find kitten. This task is complicated "
638 "by the existence of various things which are not kitten. "
639 "Robot must touch items to determine if they are kitten or "
640 "not. The game ends when robotfindskitten. You may move "
641 "robot about by tapping on any side of robot, or with the "
643 GKeyFile *desktop = g_key_file_new ();
646 HildonAppMenu *menu = HILDON_APP_MENU (hildon_app_menu_new ());
650 window = hildon_window_new ();
651 gtk_window_set_title (GTK_WINDOW (window), "robotfindskitten");
652 gtk_widget_modify_bg (window, GTK_STATE_NORMAL, &black);
653 g_signal_connect (G_OBJECT (window), "button-press-event", G_CALLBACK (on_window_clicked), NULL);
654 g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (on_key_pressed), NULL);
655 g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
659 /* Get the rather odd version string. The RFK spec says that
660 * it should read v<major>.<minor>.<number-of-NKIs>.
662 if (g_key_file_load_from_file (desktop,
663 "/usr/share/applications/hildon/rfk.desktop",
667 version = g_strdup_printf("v%s.%d",
668 g_key_file_get_value (desktop, "Desktop Entry", "Version", NULL),
670 g_key_file_free (desktop);
674 version = g_strdup("");
677 button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT,
678 HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
680 g_signal_connect (button, "clicked", G_CALLBACK (play_game), NULL);
682 gtk_box_pack_end (GTK_BOX (buttons), button, TRUE, TRUE, 0);
684 button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT,
685 HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
687 g_signal_connect (button, "clicked", G_CALLBACK (get_help), NULL);
688 gtk_box_pack_end (GTK_BOX (buttons), button, TRUE, TRUE, 0);
690 /* and another help button, this time for the menu */
691 button = gtk_button_new_with_label ("Help");
692 g_signal_connect (button, "clicked", G_CALLBACK (get_help), NULL);
693 hildon_app_menu_append (menu, GTK_BUTTON (button));
695 button = gtk_button_new_with_label ("Restart");
696 g_signal_connect (button, "clicked", G_CALLBACK (restart), NULL);
697 hildon_app_menu_append (menu, GTK_BUTTON (button));
699 gtk_widget_show_all (GTK_WIDGET (menu));
700 hildon_window_set_app_menu (HILDON_WINDOW (window), menu);
702 explain = gtk_label_new (explanation);
703 gtk_label_set_line_wrap (GTK_LABEL (explain), TRUE);
705 gtk_box_pack_end (GTK_BOX (middle), explain, TRUE, TRUE, 0);
706 gtk_box_pack_end (GTK_BOX (middle), gtk_image_new_from_pixbuf (robot_pic), FALSE, FALSE, 0);
708 intro = gtk_vbox_new (FALSE, 0);
709 gtk_box_pack_end (GTK_BOX (intro), buttons, FALSE, FALSE, 0);
710 gtk_box_pack_end (GTK_BOX (intro), middle, TRUE, TRUE, 0);
711 gtk_box_pack_end (GTK_BOX (intro), gtk_label_new (version), FALSE, FALSE, 0);
714 state_widget[STATE_PROLOGUE] = intro;
716 /* The game itself */
718 state_widget[STATE_PLAYING] = gtk_table_new (ARENA_HEIGHT, ARENA_WIDTH, TRUE);
719 g_signal_connect (state_widget[STATE_PLAYING], "parent-set", G_CALLBACK (set_up_board), NULL);
721 for (x=0; x < ARENA_WIDTH; x++)
722 for (y=0; y < ARENA_HEIGHT; y++)
726 state_widget[STATE_EPILOGUE] = gtk_drawing_area_new ();
727 g_signal_connect (state_widget[STATE_EPILOGUE], "parent-set", G_CALLBACK (ending_animation), NULL);
728 g_signal_connect (G_OBJECT (state_widget[STATE_EPILOGUE]),
729 "expose_event", G_CALLBACK (ending_animation_draw), NULL);
731 for (x=0; x<STATE_LAST; x++)
733 /* so we don't lose them when we take them offscreen */
734 g_object_ref (state_widget[x]);
738 /****************************************************************/
739 /* Let's kick the whole thing off... */
740 /****************************************************************/
746 gtk_init (&argc, &argv);
747 g_set_application_name ("robotfindskitten");
750 ensure_messages_loaded ();
754 switch_state (STATE_PROLOGUE);