added online help
[rfk] / rfk.c
1 /*  robotfindskitten for maemo
2  *  original by Leonard Richardson, 1997
3  *  ported to maemo by Thomas Thurman, 2009
4  *  suggestions welcome
5  *  Compile with:
6  *  gcc -Wall -g rfk.c -o rfk `pkg-config --cflags --libs gtk+-2.0 hildon-1 dbus-glib-1 dbus-1`
7  */
8
9 #include <dbus/dbus-glib.h>
10 #include <gtk/gtk.h>
11 #include <stdlib.h>
12 #include <glib.h>
13 #include <hildon/hildon.h>
14 #include <math.h>
15 #include <stdio.h>
16 #include <string.h>
17
18 #define ARENA_WIDTH 25
19 #define ARENA_HEIGHT 12
20
21 const int amount_of_random_stuff = 15;
22
23 GSList *nki = NULL;
24 guint nki_count = 0;
25
26 GtkWidget *arena[ARENA_WIDTH][ARENA_HEIGHT];
27 GtkWidget *intro, *table, *window, *robot, *kitten;
28 int robot_x, robot_y;
29 gboolean *used = NULL;
30
31 GdkPixbuf *robot_pic, *love_pic, *kitten_pic;
32 GtkWidget *animation_area;
33
34 const GdkColor black = { 0, };
35
36 /****************************************************************/
37 /* Random object descriptions.                                  */
38 /****************************************************************/
39
40 char *
41 description (void)
42 {
43   int r;
44    
45   do
46     {
47       r = random() % nki_count;
48     }
49   while (used[r]);
50
51   used[r] = TRUE;
52   return g_slist_nth_data (nki, r);
53 }
54
55 /****************************************************************/
56 /* Placing objects.                                             */
57 /****************************************************************/
58
59 void
60 place_in_arena_at_xy (GtkWidget *item, int x, int y)
61 {
62   arena[x][y] = item;
63
64   gtk_table_attach_defaults (GTK_TABLE (table),
65                              item,
66                              x, x+1,
67                              y, y+1);
68
69   if (item==robot)
70     {
71       robot_x = x;
72       robot_y = y;
73     }
74 }
75
76 void
77 place_in_arena_randomly (GtkWidget *item)
78 {
79   int x, y;
80    
81   do
82     {
83       x = random() % ARENA_WIDTH;
84       y = random() % ARENA_HEIGHT;
85     }
86   while (arena[x][y]);
87
88   place_in_arena_at_xy (item, x, y);
89 }
90
91 /****************************************************************/
92 /* Labels representing things the robot might find.             */
93 /****************************************************************/
94
95 GtkWidget *
96 random_character (gchar *description)
97 {
98   gchar character[2] = { random() % ('~'-'!') + '!', 0 };
99   gchar *escaped_character = g_markup_escape_text (character, -1);
100   gchar *markup = g_strdup_printf ("<span color=\"#%02x%02x%02x\">%s</span>",
101                                    (int) (random() % 0x7F)+0x80,
102                                    (int) (random() % 0x7F)+0x80,
103                                    (int) (random() % 0x7F)+0x80,
104                                    escaped_character);
105   GtkWidget *result = gtk_label_new (NULL);
106   gtk_label_set_markup (GTK_LABEL (result), markup);
107   g_free (markup);
108   g_free (escaped_character);
109
110   g_object_set_data (G_OBJECT (result), "examine", description);
111
112   return result;
113 }
114
115 /****************************************************************/
116 /* Talking back to the user.                                    */
117 /****************************************************************/
118
119 void
120 show_message (const char *message)
121 {
122   HildonNote* note = HILDON_NOTE
123     (hildon_note_new_information (GTK_WINDOW (window),
124                                   message?message:
125                                   "Some message was supposed to be here."));
126   gtk_dialog_run (GTK_DIALOG (note));
127   gtk_widget_destroy (GTK_WIDGET (note));
128 }
129
130 /****************************************************************/
131 /* Loading the non-kitten objects.                              */
132 /****************************************************************/
133 void
134 ensure_messages_loaded (void)
135 {
136   FILE *nki_file = NULL;
137   gchar *line = NULL;
138   gboolean headers = TRUE;
139
140   if (nki_count)
141     return;
142
143   nki_file = fopen ("/usr/share/rfk/non-kitten-items.rfk", "r");
144
145   if (!nki_file)
146     {
147       show_message ("Could not open list of non-kitten items!  Must quit.");
148       exit (EXIT_FAILURE);
149     }
150
151   while (!feof (nki_file))
152     {
153       char newline;
154       if (fscanf (nki_file, "%a[^\n]%c", &line, &newline) == EOF)
155         {
156           break;
157         }
158
159       if (strcmp(line, "")==0)
160         {
161           headers = FALSE;
162           fscanf (nki_file, "%c", &newline); 
163           free (line);
164         }
165       else if (headers)
166         {
167           /* we ignore all the headers for now */
168           free (line);
169         }
170       else
171         {
172           nki = g_slist_prepend (nki, line);
173           nki_count++;
174         }
175     }
176
177   fclose (nki_file);
178
179   used = g_malloc0 (nki_count);
180 }
181
182 void
183 load_images (void)
184 {
185   robot_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-robot.png", NULL);
186   love_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-love.png", NULL);
187   kitten_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-kitten.png", NULL);
188 }
189
190 /****************************************************************/
191 /* The ending animation.                                        */
192 /****************************************************************/
193
194 static gboolean
195 ending_animation_quit (gpointer data)
196 {
197   gtk_main_quit ();
198   return FALSE;
199 }
200
201 static gboolean
202 ending_animation_draw (GtkWidget *widget, GdkEventExpose *event, gpointer data)
203 {
204   /* We only run through once, so just make it static. */
205   static int cycle_count = 0;
206
207   static int robot_x = 0;
208   static int robot_stop = 0;
209   static int kitten_x = 0;
210   static int all_y = 0;
211
212   const int stepsize = 3;
213
214   if (!kitten_x)
215     {
216       all_y = (event->area.height - gdk_pixbuf_get_height (love_pic)) / 2;
217
218       robot_stop = gdk_pixbuf_get_width (robot_pic) + gdk_pixbuf_get_width (love_pic);
219       kitten_x = event->area.width - (cycle_count*stepsize + gdk_pixbuf_get_width (kitten_pic));
220     }
221
222   gdk_gc_set_foreground (widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
223                          &black);
224
225   gdk_draw_rectangle (GDK_DRAWABLE(widget->window),
226                       widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
227                       TRUE,
228                       0, 0, event->area.width, event->area.height);
229
230   gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
231                    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
232                    robot_pic, 0, 0,
233                    robot_x, all_y,
234                    -1, -1,
235                    GDK_RGB_DITHER_NONE, 0, 0);
236
237   gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
238                    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
239                    kitten_pic, 0, 0,
240                    kitten_x, all_y,
241                    -1, -1,
242                    GDK_RGB_DITHER_NONE, 0, 0);
243
244   cycle_count++;
245   robot_x += stepsize;
246   kitten_x -= stepsize;
247
248   if (robot_x+robot_stop >= kitten_x)
249     {
250       gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
251                        widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
252                        love_pic, 0, 0,
253                        robot_x + gdk_pixbuf_get_width (robot_pic), all_y,
254                        -1, -1,
255                        GDK_RGB_DITHER_NONE, 0, 0);
256
257       g_object_unref (love_pic);
258       love_pic = NULL;
259
260       g_timeout_add (2000, ending_animation_quit, NULL);
261     }
262
263   return TRUE;
264 }
265
266 static gboolean
267 ending_animation_step (gpointer data)
268 {
269   if (love_pic)
270     {
271       gdk_window_invalidate_rect (animation_area->window,
272                                   NULL, TRUE);
273
274       return TRUE;
275     }
276   else
277     return FALSE;
278 }
279
280 static void
281 ending_animation ()
282 {
283   animation_area =  gtk_drawing_area_new ();
284
285   gtk_container_remove (GTK_CONTAINER (window), GTK_WIDGET (table));
286   gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (animation_area));
287   gtk_widget_show_all (window);
288
289   g_signal_connect (G_OBJECT (animation_area),
290                     "expose_event", G_CALLBACK (ending_animation_draw), NULL);
291   g_timeout_add (10, ending_animation_step, NULL);
292 }
293
294 /****************************************************************/
295 /* Moving the robot.  Way to go, robot!                         */
296 /****************************************************************/
297
298 typedef struct {
299   guint gdk_key;
300   gchar vi_key; /* or nethack equivalent */
301   guint8 move_x;
302   guint8 move_y;
303 } direction;
304
305 direction directions[] = {
306   { GDK_Home,      'y', -1, -1 },
307   { GDK_Left,      'h', -1,  0 },
308   { GDK_End,       'b', -1,  1 },
309   { GDK_Down,      'j',  0,  1 },
310   { GDK_Page_Down, 'n',  1,  1 },
311   { GDK_Right,     'l',  1,  0 },
312   { GDK_Page_Up,   'u',  1, -1 },
313   { GDK_Up,        'k',  0, -1 }
314 };
315
316 gboolean
317 move_robot (guint8 whichway)
318 {
319   GtkWidget *new_space;
320   gint8 dx = directions[whichway].move_x;
321   gint8 dy = directions[whichway].move_y;
322
323   const char *found;
324
325   if (robot_x+dx<0 ||
326       robot_y+dy<0 ||
327       robot_x+dx>=ARENA_WIDTH ||
328       robot_y+dy>=ARENA_HEIGHT)
329     return TRUE;
330
331   new_space = arena[robot_x+dx][robot_y+dy];
332   found = g_object_get_data (G_OBJECT (new_space), "examine");
333
334   if (found && *found)
335     {
336       show_message (found);
337
338       if (new_space == kitten)
339         {
340           ending_animation ();
341         }
342
343       return TRUE;
344     }
345   else
346     {
347       /* just an ordinary move into an empty space */
348
349       g_object_ref (new_space);
350
351       gtk_container_remove (GTK_CONTAINER (table), robot);
352       gtk_container_remove (GTK_CONTAINER (table), new_space);
353
354       place_in_arena_at_xy (new_space, robot_x, robot_y);
355       place_in_arena_at_xy (robot, robot_x+dx, robot_y+dy);
356
357       g_object_unref (new_space);
358
359       return FALSE;
360     }
361 }
362
363 /****************************************************************/
364 /* Event handlers.                                              */
365 /****************************************************************/
366
367 gboolean
368 on_window_clicked (GtkWidget      *widget,
369                    GdkEventButton *event,
370                    gpointer        user_data)
371 {
372   /** Centre point of robot's representation on screen */
373   int rx, ry;
374   double angle;
375
376   rx = (robot->allocation.x+robot->allocation.width/2);
377   ry = (robot->allocation.y+robot->allocation.height/2);
378
379   angle = atan2(event->x - rx,
380                 event->y - ry) +
381     M_PI * (9/8);
382
383   move_robot (((int) (angle / (M_PI/4))) % 8);
384
385   return TRUE;
386 }
387
388 gboolean
389 on_key_pressed (GtkWidget      *widget,
390                 GdkEventKey    *event,
391                 gpointer        user_data)
392 {
393   gint i;
394   guint keyval = event->keyval;
395
396   if (keyval>='A' && keyval<='Z')
397     {
398       keyval += ('a'-'A');
399     }
400
401   for (i=0; i<G_N_ELEMENTS(directions); i++)
402     {
403       if (keyval==directions[i].gdk_key ||
404           keyval==directions[i].vi_key)
405         {
406           if (event->state & GDK_SHIFT_MASK)
407             {
408               while (!move_robot (i))
409                 {
410                   /* keep going, robot! */
411                 }
412             }
413           else
414             {
415               move_robot (i);
416             }
417           return FALSE;
418         }
419     }
420
421   return FALSE;
422 }
423
424 void
425 create_window (void)
426 {
427   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
428   gtk_window_set_title (GTK_WINDOW (window), "robotfindskitten");
429   gtk_widget_modify_bg (window, GTK_STATE_NORMAL, &black);
430 }
431
432 void
433 set_up_game (void)
434 {
435   guint x, y;
436
437   g_signal_connect (G_OBJECT (window), "button-press-event", G_CALLBACK (on_window_clicked), NULL);
438   g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (on_key_pressed), NULL);
439   g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
440   gdk_window_set_events (GTK_WIDGET (window)->window,
441                          gdk_window_get_events(GTK_WIDGET (window)->window) | GDK_BUTTON_PRESS_MASK);
442         
443   table = gtk_table_new (ARENA_HEIGHT, ARENA_WIDTH, TRUE);
444   gtk_container_remove (GTK_CONTAINER (window), GTK_WIDGET (intro));
445   gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (table));
446
447   robot = gtk_label_new ("#");
448   g_object_ref (robot);
449   kitten = random_character ("You found kitten!  Way to go, robot!");
450
451   place_in_arena_randomly (robot);
452   place_in_arena_randomly (kitten);
453
454   if (nki_count < amount_of_random_stuff)
455     {
456       gtk_widget_show_all (window);
457       show_message ("There are too few non-kitten items to play a meaningful game.");
458       exit (EXIT_FAILURE);
459     }
460
461   for (x=0; x < amount_of_random_stuff; x++)
462     place_in_arena_randomly (random_character (description ()));
463
464   for (x=0; x < ARENA_WIDTH; x++)
465     for (y=0; y < ARENA_HEIGHT; y++)
466       if (!arena[x][y])
467         place_in_arena_at_xy (gtk_label_new (NULL), x, y);
468
469   gtk_widget_show_all (window);
470 }
471
472 /****************************************************************/
473 /* Online help.                                                 */
474 /****************************************************************/
475 gboolean
476 get_help (gpointer button, gpointer data)
477 {
478   DBusGConnection *connection;
479   GError *error = NULL;
480   DBusGProxy *proxy;
481
482   connection = dbus_g_bus_get (DBUS_BUS_SESSION,
483                                &error);
484   if (connection == NULL)
485     {
486       show_message (error->message);
487       g_error_free (error);
488       return FALSE;
489     }
490
491   proxy = dbus_g_proxy_new_for_name (connection,
492                                      "com.nokia.osso_browser",
493                                      "/com/nokia/osso_browser/request",
494                                      "com.nokia.osso_browser");
495
496   error = NULL;
497   if (!dbus_g_proxy_call (proxy, "load_url", &error,
498                           G_TYPE_STRING, "/usr/share/rfk/help.html",
499                           G_TYPE_INVALID,
500                           G_TYPE_INVALID))
501     {
502       show_message (error->message);
503       g_error_free (error);
504       return FALSE;
505     }
506   return FALSE;
507 }
508
509 void
510 play_game (gpointer button, gpointer data)
511 {
512   set_up_game ();
513 }
514
515 void
516 show_intro (void)
517 {
518   GtkWidget *middle = gtk_hbox_new (FALSE, 0);
519   GtkWidget *buttons = gtk_hbox_new (TRUE, 0);
520   GtkWidget *explain = NULL, *help_button, *play_button;
521   const char *explanation =
522     "In this game, you are robot (#). "
523     "Your job is to find kitten. This task is complicated "
524     "by the existence of various things which are not kitten. "
525     "Robot must touch items to determine if they are kitten or "
526     "not. The game ends when robotfindskitten. You may move "
527     "robot about by tapping on any side of robot, or with the "
528     "arrow keys.";
529   GKeyFile *desktop = g_key_file_new ();
530   gchar *version;
531
532   if (g_key_file_load_from_file (desktop,
533                                  "/usr/share/applications/hildon/rfk.desktop",
534                                  G_KEY_FILE_NONE,
535                                  NULL))
536     {
537       version = g_strdup_printf("v%s.%d",
538                                 g_key_file_get_value (desktop, "Desktop Entry", "Version", NULL),
539                                 nki_count);
540       g_key_file_free (desktop);
541     }
542   else
543     {
544       version = g_strdup("");
545     }
546
547   help_button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT,
548                                              HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
549                                              "Help", NULL);
550   g_signal_connect (help_button, "clicked", G_CALLBACK (get_help), NULL);
551
552   play_button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT,
553                                              HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
554                                              "Play", NULL);
555   g_signal_connect (play_button, "clicked", G_CALLBACK (play_game), NULL);
556
557   gtk_box_pack_end (buttons, play_button, TRUE, TRUE, 0);
558   gtk_box_pack_end (buttons, help_button, TRUE, TRUE, 0);
559
560   explain = gtk_label_new (explanation);
561   gtk_label_set_line_wrap (explain, TRUE);
562
563   gtk_box_pack_end (middle, explain, TRUE, TRUE, 0);
564   gtk_box_pack_end (middle, gtk_image_new_from_pixbuf (robot_pic), FALSE, FALSE, 0);
565
566   intro = gtk_vbox_new (FALSE, 0);
567   gtk_box_pack_end (GTK_BOX (intro), buttons, FALSE, FALSE, 0);
568   gtk_box_pack_end (GTK_BOX (intro), middle, TRUE, TRUE, 0);
569   gtk_box_pack_end (GTK_BOX (intro), gtk_label_new (version), FALSE, FALSE, 0);
570
571   g_free (version);
572
573   gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (intro));
574
575   gtk_widget_show_all (window);
576 }
577
578 /****************************************************************/
579 /* Let's kick the whole thing off...                            */
580 /****************************************************************/
581
582 int
583 main (gint argc,
584       gchar **argv)
585 {
586   gtk_init (&argc, &argv);
587   g_set_application_name ("robotfindskitten");
588   srandom (time(0));
589
590   ensure_messages_loaded ();
591   load_images ();
592
593   create_window ();
594   show_intro ();
595
596   gtk_main ();
597
598   return EXIT_SUCCESS;
599 }