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