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