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