( t m ) replaced with trademark symbol
[rfk] / rfk.c
diff --git a/rfk.c b/rfk.c
index ad3da36..55be231 100644 (file)
--- a/rfk.c
+++ b/rfk.c
  *  ported to maemo by Thomas Thurman, 2009
  *  suggestions welcome
  *  Compile with:
- *  gcc -Wall -g rfk.c -o rfk `pkg-config --cflags --libs gtk+-2.0 hildon-1`
+ *  gcc -Wall -g rfk.c -o rfk `pkg-config --cflags --libs gtk+-2.0 hildon-1 dbus-glib-1 dbus-1`
  */
 
+#include <dbus/dbus-glib.h>
 #include <gtk/gtk.h>
 #include <stdlib.h>
 #include <glib.h>
 #include <hildon/hildon.h>
 #include <math.h>
+#include <stdio.h>
+#include <string.h>
 
 #define ARENA_WIDTH 25
 #define ARENA_HEIGHT 12
 
 const int amount_of_random_stuff = 15;
 
-const char *explanation =
-  "In this game, you are robot (#). "
-  "Your job is to find kitten. This task is complicated "
-  "by the existence of various things which are not kitten. "
-  "Robot must touch items to determine if they are kitten or "
-  "not. The game ends when robotfindskitten. You may move "
-  "robot about by tapping on any side of robot, or with the "
-  "cursor keys.";
-
-static char* messages[] =
-  {
-    "\"I pity the fool who mistakes me for kitten!\", sez Mr. T.",
-    "That's just an old tin can.",
-    "It's an altar to the horse god.",
-    "A box of dancing mechanical pencils. They dance! They sing!",
-    "It's an old Duke Ellington record.",
-    "A box of fumigation pellets.",
-    "A digital clock. It's stuck at 2:17 PM.",
-    "That's just a charred human corpse.",
-    "I don't know what that is, but it's not kitten.",
-    "An empty shopping bag. Paper or plastic?",
-    "Could it be... a big ugly bowling trophy?",
-    "A coat hanger hovers in thin air. Odd.",
-    "Not kitten, just a packet of Kool-Aid(tm).",
-    "A freshly-baked pumpkin pie.",
-    "A lone, forgotten comma, sits here, sobbing.",
-    "ONE HUNDRED THOUSAND CARPET FIBERS!!!!!",
-    "It's Richard Nixon's nose!",
-    "It's Lucy Ricardo. \"Aaaah, Ricky!\", she says.",
-    "You stumble upon Bill Gates' stand-up act.",
-    "Just an autographed copy of the Kama Sutra.",
-    "It's the Will Rogers Highway. Who was Will Rogers, anyway?",
-    "It's another robot, more advanced in design than you but strangely immobile.",
-    "Leonard Richardson is here, asking people to lick him.",
-    "It's a stupid mask, fashioned after a beagle.",
-    "Your State Farm Insurance(tm) representative!",
-    "It's the local draft board.",
-    "Seven 1/4\" screws and a piece of plastic.",
-    "An 80286 machine.",
-    "One of those stupid \"Homes of the Stars\" maps.",
-    "A signpost saying \"TO KITTEN\". It points in no particular direction.",
-    "A hammock stretched between a tree and a volleyball pole.",
-    "A Texas Instruments of Destruction calculator.",
-    "It's a dark, amphorous blob of matter.",
-    "Just a pincushion.",
-    "It's a mighty zombie talking about some love and prosperity.",
-    "\"Dear robot, you may have already won our 10 MILLION DOLLAR prize...\"",
-    "It's just an object.",
-    "A mere collection of pixels.",
-    "A badly dented high-hat cymbal lies on its side here.",
-    "A marijuana brownie.",
-    "A plush Chewbacca.",
-    "Daily hunger conditioner from Australasia",
-    "Just some stuff.",
-    "Why are you touching this when you should be finding kitten?",
-    "A glorious fan of peacock feathers.",
-    "It's some compromising photos of Babar the Elephant.",
-    "A copy of the Weekly World News. Watch out for the chambered nautilus!",
-    "It's the proverbial wet blanket.",
-    "A \"Get Out of Jail Free\" card.",
-    "An incredibly expensive \"Mad About You\" collector plate.",
-    "Paul Moyer's necktie.",
-    "A haircut and a real job. Now you know where to get one!",
-    "An automated robot-hater. It frowns disapprovingly at you.",
-    "An automated robot-liker. It smiles at you.",
-    "It's a black hole. Don't fall in!",
-    "Just a big brick wall.",
-    "You found kitten! No, just kidding.",
-    "Heart of Darkness brand pistachio nuts.",
-    "A smoking branding iron shaped like a 24-pin connector.",
-    "It's a Java applet.",
-    "An abandoned used-car lot.",
-    "A shameless plug for Maemo.",
-    "A shameless plug for the UCLA Linux Users Group: http://linux.ucla.edu/",
-    "A can of Spam Lite.",
-    "This is another fine mess you've gotten us into, Stanley.",
-    "It's scenery for \"Waiting for Godot\".",
-    "This grain elevator towers high above you.",
-    "A Mentos wrapper.",
-    "It's the constellation Pisces.",
-    "It's a fly on the wall. Hi, fly!",
-    "This kind of looks like kitten, but it's not.",
-    "It's a banana! Oh, joy!",
-    "A helicopter has crashed here.",
-    "Carlos Tarango stands here, doing his best impression of Pat Smear.",
-    "A patch of mushrooms grows here.",
-    "A patch of grape jelly grows here.",
-    "A spindle, and a grindle, and a bucka-wacka-woom!",
-    "A geyser sprays water high into the air.",
-    "A toenail? What good is a toenail?",
-    "You've found the fish! Not that it does you much good in this game.",
-    "A Buttertonsils bar.",
-    "One of the few remaining discoes.",
-    "Ah, the uniform of a Revolutionary-era minuteman.",
-    "A punch bowl, filled with punch and lemon slices.",
-    "It's nothing but a G-thang, baby.",
-    "IT'S ALIVE! AH HA HA HA HA!",
-    "This was no boating accident!",
-    "Wait! This isn't the poker chip! You've been tricked! DAMN YOU, MENDEZ!",
-    "A livery stable! Get your livery!",
-    "It's a perpetual immobility machine.",
-    "\"On this spot in 1962, Henry Winkler was sick.\"",
-    "There's nothing here; it's just an optical illusion.",
-    "The World's Biggest Motzah Ball!",
-    "A tribe of cannibals lives here. They eat Malt-O-Meal for breakfast, you know.",
-    "This appears to be a rather large stack of trashy romance novels.",
-    "Look out! Exclamation points!",
-    "A herd of wild coffee mugs slumbers here.",
-    "It's a limbo bar! How low can you go?",
-    "It's the horizon. Now THAT'S weird.",
-    "A vase full of artificial flowers is stuck to the floor here.",
-    "A large snake bars your way.",
-    "A pair of saloon-style doors swing slowly back and forth here.",
-    "It's an ordinary bust of Beethoven... but why is it painted green?",
-    "It's TV's lovable wisecracking Crow! \"Bite me!\", he says.",
-    "Hey, look, it's war. What is it good for? Absolutely nothing. Say it again.",
-    "It's the amazing self-referential thing that's not kitten.",
-    "A flamboyant feather boa. Now you can dress up like Carol Channing!",
-    "\"Sure hope we get some rain soon,\" says Farmer Joe.",
-    "\"How in heck can I wash my neck if it ain't gonna rain no more?\" asks Farmer Al.",
-    "\"Topsoil's all gone, ma,\" weeps Lil' Greg.",
-    "This is a large brown bear. Oddly enough, it's currently peeing in the woods.",
-    "A team of arctic explorers is camped here.",
-    "This object here appears to be Louis Farrakhan's bow tie.",
-    "This is the world-famous Chain of Jockstraps.",
-    "A trash compactor, compacting away.",
-    "This toaster strudel is riddled with bullet holes!",
-    "It's a hologram of a crashed helicopter.",
-    "This is a television. On screen you see a robot strangely similar to yourself.",
-    "This balogna has a first name, it's R-A-N-C-I-D.",
-    "A salmon hatchery? Look again. It's merely a single salmon.",
-    "It's a rim shot. Ba-da-boom!",
-    "It's creepy and it's kooky, mysterious and spooky. It's also somewhat ooky.",
-    "This is an anagram.",
-    "This object is like an analogy.",
-    "It's a symbol. You see in it a model for all symbols everywhere.",
-    "The object pushes back at you.",
-    "A traffic signal. It appears to have been recently vandalized.",
-    "\"There is no kitten!\" cackles the old crone. You are shocked by her blasphemy.",
-    "This is a Lagrange point. Don't come too close now.",
-    "The dirty old tramp bemoans the loss of his harmonica.",
-    "Look, it's Fanny the Irishman!",
-    "What in blazes is this?",
-    "It's the instruction manual for a previous version of this game.",
-    "A brain cell. Oddly enough, it seems to be functioning.",
-    "Tea and/or crumpets.",
-    "This jukebox has nothing but Cliff Richards albums in it.",
-    "It's a Quaker Oatmeal tube, converted into a drum.",
-    "This is a remote control. Being a robot, you keep a wide berth.",
-    "It's a roll of industrial-strength copper wire.",
-    "Oh boy! Grub! Er, grubs.",
-    "A puddle of mud, where the mudskippers play.",
-    "Plenty of nothing.",
-    "Look at that, it's the Crudmobile.",
-    "Just Walter Mattheau and Jack Lemmon.",
-    "Two crepes, two crepes in a box.",
-    "An autographed copy of \"Primary Colors\", by Anonymous.",
-    "Another rabbit? That's three today!",
-    "It's a segmentation fault. Core dumped, by the way.",
-    "A historical marker showing the actual location of /dev/null.",
-    "Thar's Mobius Dick, the convoluted whale. Arrr!",
-    "It's a charcoal briquette, smoking away.",
-    "A pizza, melting in the sun.",
-    "It's a \"HOME ALONE 2: Lost in New York\" novelty cup.",
-    "A stack of 7 inch floppies wobbles precariously.",
-    "It's nothing but a corrupted floppy. Coaster anyone?",
-    "A section of glowing phosphor cells sings a song of radiation to you.",
-    "This TRS-80 III is eerily silent.",
-    "A toilet bowl occupies this space.",
-    "This peg-leg is stuck in a knothole!",
-    "It's a solitary vaccuum tube.",
-    "This corroded robot is clutching a mitten.",
-    "\"Hi, I'm Anson Williams, TV's 'Potsy'.\"",
-    "This subwoofer was blown out in 1974.",
-    "Three half-pennies and a wooden nickel.",
-    "It's the missing chapter to \"A Clockwork Orange\".",
-    "It's a burrito stand flyer. \"Taqueria El Ranchito\".",
-    "This smiling family is happy because they eat LARD.",
-    "Roger Avery, persona un famoso de los Estados Unidos.",
-    "Ne'er but a potted plant.",
-    "A parrot, kipping on its back.",
-    "A forgotten telephone switchboard.",
-    "A forgotten telephone switchboard operator.",
-    "It's an automated robot-disdainer. It pretends you're not there.",
-    "It's a portable hole. A sign reads: \"Closed for the winter\".",
-    "Just a moldy loaf of bread.",
-    "A little glass tub of Carmex. ($.89) Too bad you have no lips.",
-    "A Swiss-Army knife. All of its appendages are out. (toothpick lost)",
-    "It's a zen simulation, trapped within an ASCII character.",
-    "It's a copy of \"The Rubaiyat of Spike Schudy\".",
-    "It's \"War and Peace\" (unabridged, very small print).",
-    "A willing, ripe tomato bemoans your inability to digest fruit.",
-    "A robot comedian. You feel amused.",
-    "It's KITT, the talking car.",
-    "Here's Pete Peterson. His batteries seem to have long gone dead.",
-    "\"Blup, blup, blup\", says the mud pot.",
-    "More grist for the mill.",
-    "Grind 'em up, spit 'em out, they're twigs.",
-    "The boom box cranks out an old Ethel Merman tune.",
-    "It's \"Finding kitten\", published by O'Reilly and Associates.",
-    "Pumpkin pie spice.",
-    "It's the Bass-Matic '76! Mmm, that's good bass!",
-    "\"Lend us a fiver 'til Thursday\", pleas Andy Capp.",
-    "It's a tape of '70s rock. All original hits! All original artists!",
-    "You've found the fabled America Online disk graveyard!",
-    "Empty jewelboxes litter the landscape.",
-    "It's the astounding meta-object.",
-    "Ed McMahon stands here, lost in thought. Seeing you, he bellows, \"YES SIR!\"",
-    "...thingy???",
-    "It's 1000 secrets the government doesn't want you to know!",
-    "The letters O and R.",
-    "A magical... magic thing.",
-    "It's a moment of silence.",
-    "It's Sirhan-Sirhan, looking guilty.",
-    "It's \"Chicken Soup for the Kitten-seeking Soulless Robot.\"",
-    "It is a set of wind-up chatter teeth.",
-    "It is a cloud shaped like an ox.",
-    "You see a snowflake here, melting slowly.",
-    "It's a big block of ice. Something seems to be frozen inside it.",
-    "Vladimir Lenin's casket rests here.",
-    "It's a copy of \"Zen and The Art of Robot Maintenance\".",
-    "This invisible box contains a pantomime horse.",
-    "A mason jar lies here open. It's label reads: \"do not open!\".",
-    "A train of thought chugs through here.",
-    "This jar of pickles expired in 1957.",
-    "Someone's identity disk lies here.",
-    "\"Yes!\" says the bit.",
-    "\"No!\" says the bit.",
-    "A dodecahedron bars your way.",
-    "Mr. Hooper is here, surfing.",
-    "It's a big smoking fish.",
-    "You have new mail in /var/spool/robot",
-    "Just a monitor with the blue element burnt out.",
-    "A pile of coaxial plumbing lies here.",
-    "It's a rotten old shoe.",
-    "It's a hundred-dollar bill.",
-    "It's a Dvorak keyboard.",
-    "It's a cardboard box full of 8-tracks.",
-    "Just a broken hard drive containg the archives of Nerth Pork.",
-    "A broken metronome sits here, it's needle off to one side.",
-    "A sign reads: \"Go home!\"",
-    "A sign reads: \"No robots allowed!\"",
-    "It's the handheld robotfindskitten game, by Tiger.",
-    "This particular monstrosity appears to be ENIAC.",
-    "This is a tasty-looking banana creme pie.",
-    "A wireframe model of a hot dog rotates in space here.",
-    "Just the empty husk of a locust.",
-    "You disturb a murder of crows.",
-    "It's a copy of the robotfindskitten EULA.",
-    "It's Death.",
-    "It's an autographed copy of \"Secondary Colors,\" by Bob Ross.",
-    "It is a marzipan dreadnought that appears to have melted and stuck.",
-    "It's a DVD of \"Crouching Monkey, Hidden Kitten\", region encoded for the moon.",
-    "It's Kieran Hervold.  Damn dyslexia!",
-    "A non-descript box of crackers.",
-    "Carbonated Water, High Fructose Corn Syrup, Color, Phosphoric Acid, Flavors, Caffeine.",
-    "\"Move along! Nothing to see here!\"",
-    "It's the embalmed corpse of Vladimir Lenin.",
-    "A coupon for one free steak-fish at your local family diner.",
-    "A set of keys to a 2001 Rolls Royce. Worthless."
-  };
+typedef enum {
+  STATE_PROLOGUE,
+  STATE_PLAYING,
+  STATE_EPILOGUE,
+  STATE_LAST
+} StateOfPlay;
+
+StateOfPlay current_state = STATE_LAST;
+GtkWidget* state_widget[STATE_LAST];
+
+GSList *nki = NULL;
+guint nki_count = 0;
 
 GtkWidget *arena[ARENA_WIDTH][ARENA_HEIGHT];
-GtkWidget *table, *window, *robot, *kitten;
+GtkWidget *window, *robot, *kitten;
 int robot_x, robot_y;
-gboolean used[G_N_ELEMENTS (messages)] = { 0, };
+gboolean *used = NULL;
 
 GdkPixbuf *robot_pic, *love_pic, *kitten_pic;
-GtkWidget *animation_area;
 
 const GdkColor black = { 0, };
 
@@ -293,32 +46,31 @@ const GdkColor black = { 0, };
 /* Random object descriptions.                                  */
 /****************************************************************/
 
-char *
+static char *
 description (void)
 {
   int r;
    
   do
     {
-      r = random() % G_N_ELEMENTS (messages);
+      r = random() % nki_count;
     }
   while (used[r]);
-
   used[r] = TRUE;
-  return messages[r];
-       
+
+  return g_slist_nth_data (nki, r);
 }
 
 /****************************************************************/
 /* Placing objects.                                             */
 /****************************************************************/
 
-void
+static void
 place_in_arena_at_xy (GtkWidget *item, int x, int y)
 {
   arena[x][y] = item;
 
-  gtk_table_attach_defaults (GTK_TABLE (table),
+  gtk_table_attach_defaults (GTK_TABLE (state_widget[STATE_PLAYING]),
                             item,
                             x, x+1,
                             y, y+1);
@@ -330,7 +82,7 @@ place_in_arena_at_xy (GtkWidget *item, int x, int y)
     }
 }
 
-void
+static void
 place_in_arena_randomly (GtkWidget *item)
 {
   int x, y;
@@ -349,7 +101,7 @@ place_in_arena_randomly (GtkWidget *item)
 /* Labels representing things the robot might find.             */
 /****************************************************************/
 
-GtkWidget *
+static GtkWidget *
 random_character (gchar *description)
 {
   gchar character[2] = { random() % ('~'-'!') + '!', 0 };
@@ -373,87 +125,260 @@ random_character (gchar *description)
 /* Talking back to the user.                                    */
 /****************************************************************/
 
-void
+static void
 show_message (const char *message)
 {
   HildonNote* note = HILDON_NOTE
     (hildon_note_new_information (GTK_WINDOW (window),
-                                 message));
+                                 message?message:
+                                 "Some message was supposed to be here."));
   gtk_dialog_run (GTK_DIALOG (note));
   gtk_widget_destroy (GTK_WIDGET (note));
 }
 
 /****************************************************************/
+/* Loading the non-kitten objects.                              */
+/****************************************************************/
+
+static void
+ensure_messages_loaded (void)
+{
+  FILE *nki_file = NULL;
+  gchar *line = NULL;
+  gboolean headers = TRUE;
+
+  if (nki_count)
+    return;
+
+  nki_file = fopen ("/usr/share/rfk/non-kitten-items.rfk", "r");
+
+  if (!nki_file)
+    {
+      show_message ("Could not open list of non-kitten items!  Must quit.");
+      exit (EXIT_FAILURE);
+    }
+
+  while (!feof (nki_file))
+    {
+      char newline;
+      if (fscanf (nki_file, "%a[^\n]%c", &line, &newline) == EOF)
+       {
+         break;
+       }
+
+      if (strcmp(line, "")==0)
+       {
+         headers = FALSE;
+         fscanf (nki_file, "%c", &newline); 
+         free (line);
+       }
+      else if (headers)
+       {
+         /* we ignore all the headers for now */
+         free (line);
+       }
+      else
+       {
+         nki = g_slist_prepend (nki, line);
+         nki_count++;
+       }
+    }
+
+  fclose (nki_file);
+}
+
+static void
+load_images (void)
+{
+  robot_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-robot.png", NULL);
+  love_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-love.png", NULL);
+  kitten_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-kitten.png", NULL);
+}
+
+/****************************************************************/
+/* Stop doing that, and do something else.                      */
+/****************************************************************/
+
+static void
+switch_state (StateOfPlay new_state)
+{
+  if (current_state != STATE_LAST)
+    {
+      gtk_container_remove (GTK_CONTAINER (window), state_widget[current_state]);
+    }
+  gtk_container_add (GTK_CONTAINER (window), state_widget[new_state]);
+
+  gtk_widget_show_all (window);
+  gdk_window_set_events (GTK_WIDGET (window)->window,
+                        gdk_window_get_events(GTK_WIDGET (window)->window) | GDK_BUTTON_PRESS_MASK);
+
+  current_state = new_state;
+}
+
+/****************************************************************/
+/* Things we need DBus for: online help, and vibration.         */
+/****************************************************************/
+
+static void
+call_dbus (DBusBusType type,
+          char *name,
+          char *path,
+          char *interface,
+          char *method,
+          char *parameter)
+{
+  DBusGConnection *connection;
+  GError *error = NULL;
+
+  DBusGProxy *proxy;
+
+  connection = dbus_g_bus_get (type,
+                               &error);
+  if (connection == NULL)
+    {
+      show_message (error->message);
+      g_error_free (error);
+      return;
+    }
+
+  proxy = dbus_g_proxy_new_for_name (connection, name, path, interface);
+
+  error = NULL;
+  if (!dbus_g_proxy_call (proxy, method, &error,
+                         G_TYPE_STRING, parameter,
+                         G_TYPE_INVALID,
+                         G_TYPE_INVALID))
+    {
+      show_message (error->message);
+      g_error_free (error);
+    }
+}
+
+static gboolean
+get_help (gpointer button, gpointer data)
+{
+  call_dbus (DBUS_BUS_SESSION,
+            "com.nokia.osso_browser",
+            "/com/nokia/osso_browser/request",
+            "com.nokia.osso_browser",
+            "load_url",
+            "/usr/share/rfk/help.html");
+  return FALSE;
+}
+
+static void
+vibrate (void)
+{
+  call_dbus (DBUS_BUS_SYSTEM,
+            "com.nokia.mce",
+            "/com/nokia/mce/request",
+            "com.nokia.mce.request",
+            "req_vibrator_pattern_activate",
+            "PatternIncomingMessage");
+}
+
+/****************************************************************/
 /* The ending animation.                                        */
 /****************************************************************/
 
+gboolean animation_running = FALSE;
+
 static gboolean
 ending_animation_quit (gpointer data)
 {
-  gtk_main_quit ();
+  switch_state (STATE_PROLOGUE);
   return FALSE;
 }
 
 static gboolean
 ending_animation_draw (GtkWidget *widget, GdkEventExpose *event, gpointer data)
 {
-  /* We only run through once, so just make it static. */
   static int cycle_count = 0;
 
   static int robot_x = 0;
   static int robot_stop = 0;
   static int kitten_x = 0;
   static int all_y = 0;
+  static GdkGC *gc = NULL;
 
   const int stepsize = 3;
+  static int love_size = 40;
 
   if (!kitten_x)
     {
+      gc = gdk_gc_new (GDK_DRAWABLE (widget->window));
+
       all_y = (event->area.height - gdk_pixbuf_get_height (love_pic)) / 2;
 
       robot_stop = gdk_pixbuf_get_width (robot_pic) + gdk_pixbuf_get_width (love_pic);
       kitten_x = event->area.width - (cycle_count*stepsize + gdk_pixbuf_get_width (kitten_pic));
     }
 
-  gdk_gc_set_foreground (widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
-                        &black);
+  gdk_gc_set_foreground (gc, &black);
 
-  gdk_draw_rectangle (GDK_DRAWABLE(widget->window),
-                     widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+  gdk_draw_rectangle (GDK_DRAWABLE (widget->window),
+                     gc,
                      TRUE,
                      0, 0, event->area.width, event->area.height);
 
-  gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
-                  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+  gdk_draw_pixbuf (GDK_DRAWABLE (widget->window),
+                  gc,
                   robot_pic, 0, 0,
                   robot_x, all_y,
                   -1, -1,
                   GDK_RGB_DITHER_NONE, 0, 0);
 
-  gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
-                  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+  gdk_draw_pixbuf (GDK_DRAWABLE (widget->window),
+                  gc,
                   kitten_pic, 0, 0,
                   kitten_x, all_y,
                   -1, -1,
                   GDK_RGB_DITHER_NONE, 0, 0);
 
-  cycle_count++;
-  robot_x += stepsize;
-  kitten_x -= stepsize;
-
-  if (robot_x+robot_stop >= kitten_x)
+  if (robot_x+robot_stop < kitten_x)
     {
+      cycle_count++;
+      robot_x += stepsize;
+      kitten_x -= stepsize;
+    }
+  else
+    {
+      GdkPixbuf *scaled_love_pic =
+       gdk_pixbuf_scale_simple (love_pic,
+                                love_size,
+                                love_size,
+                                GDK_INTERP_BILINEAR);
+
       gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
-                      widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
-                      love_pic, 0, 0,
-                      robot_x + gdk_pixbuf_get_width (robot_pic), all_y,
+                      gc,
+                      scaled_love_pic, 0, 0,
+                      robot_x + gdk_pixbuf_get_width (robot_pic) +
+                      (gdk_pixbuf_get_width (love_pic)-love_size)/2,
+                      all_y + (gdk_pixbuf_get_height (love_pic)-love_size)/2,
                       -1, -1,
                       GDK_RGB_DITHER_NONE, 0, 0);
 
-      g_object_unref (love_pic);
-      love_pic = NULL;
+      love_size += 10;
 
-      g_timeout_add (2000, ending_animation_quit, NULL);
+      if (love_size >= gdk_pixbuf_get_width (love_pic))
+       {
+         /* all done! */
+         
+         vibrate ();
+         
+         animation_running = FALSE;
+
+         g_timeout_add (2000, ending_animation_quit, NULL);
+
+         gdk_gc_unref (gc);
+         love_size = 40;
+         cycle_count = 0;
+         robot_x = 0;
+         robot_stop = 0;
+         kitten_x = 0;
+         all_y = 0;
+         gc = NULL;
+       }
     }
 
   return TRUE;
@@ -462,9 +387,9 @@ ending_animation_draw (GtkWidget *widget, GdkEventExpose *event, gpointer data)
 static gboolean
 ending_animation_step (gpointer data)
 {
-  if (love_pic)
+  if (animation_running)
     {
-      gdk_window_invalidate_rect (animation_area->window,
+      gdk_window_invalidate_rect (state_widget[STATE_EPILOGUE]->window,
                                  NULL, TRUE);
 
       return TRUE;
@@ -476,18 +401,11 @@ ending_animation_step (gpointer data)
 static void
 ending_animation ()
 {
-  robot_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-robot.png", NULL);
-  love_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-love.png", NULL);
-  kitten_pic = gdk_pixbuf_new_from_file ("/usr/share/rfk/rfk-kitten.png", NULL);
-  animation_area =  gtk_drawing_area_new ();
-
-  gtk_container_remove (GTK_CONTAINER (window), GTK_WIDGET (table));
-  gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (animation_area));
-  gtk_widget_show_all (window);
-
-  g_signal_connect (G_OBJECT (animation_area),
-                   "expose_event", G_CALLBACK (ending_animation_draw), NULL);
-  g_timeout_add (10, ending_animation_step, NULL);
+  if (current_state!=STATE_EPILOGUE)
+    {
+      animation_running = TRUE;
+      g_timeout_add (10, ending_animation_step, NULL);
+    }
 }
 
 /****************************************************************/
@@ -512,7 +430,7 @@ direction directions[] = {
   { GDK_Up,        'k',  0, -1 }
 };
 
-gboolean
+static gboolean
 move_robot (guint8 whichway)
 {
   GtkWidget *new_space;
@@ -536,7 +454,7 @@ move_robot (guint8 whichway)
 
       if (new_space == kitten)
        {
-         ending_animation ();
+         switch_state (STATE_EPILOGUE);
        }
 
       return TRUE;
@@ -547,8 +465,8 @@ move_robot (guint8 whichway)
 
       g_object_ref (new_space);
 
-      gtk_container_remove (GTK_CONTAINER (table), robot);
-      gtk_container_remove (GTK_CONTAINER (table), new_space);
+      gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]), robot);
+      gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]), new_space);
 
       place_in_arena_at_xy (new_space, robot_x, robot_y);
       place_in_arena_at_xy (robot, robot_x+dx, robot_y+dy);
@@ -563,7 +481,7 @@ move_robot (guint8 whichway)
 /* Event handlers.                                              */
 /****************************************************************/
 
-gboolean
+static gboolean
 on_window_clicked (GtkWidget      *widget,
                   GdkEventButton *event,
                   gpointer        user_data)
@@ -572,20 +490,24 @@ on_window_clicked (GtkWidget      *widget,
   int rx, ry;
   double angle;
 
+  if (current_state!=STATE_PLAYING)
+    {
+      return TRUE;
+    }
+
   rx = (robot->allocation.x+robot->allocation.width/2);
   ry = (robot->allocation.y+robot->allocation.height/2);
 
   angle = atan2(event->x - rx,
                event->y - ry) +
-    M_PI +
-    M_PI/8;
+    M_PI * (9/8);
 
-  move_robot (((int) (angle / (M_PI/4)))-1);
+  move_robot (((int) (angle / (M_PI/4))) % 8);
 
   return TRUE;
 }
 
-gboolean
+static gboolean
 on_key_pressed (GtkWidget      *widget,
                GdkEventKey    *event,
                gpointer        user_data)
@@ -593,6 +515,11 @@ on_key_pressed (GtkWidget      *widget,
   gint i;
   guint keyval = event->keyval;
 
+  if (current_state!=STATE_PLAYING)
+    {
+      return FALSE;
+    }
+
   if (keyval>='A' && keyval<='Z')
     {
       keyval += ('a'-'A');
@@ -618,57 +545,215 @@ on_key_pressed (GtkWidget      *widget,
        }
     }
 
+  if (keyval=='d' && event->state & GDK_CONTROL_MASK)
+    {
+      /* secret debugging key */
+      show_message (gtk_label_get_text (GTK_LABEL (kitten)));
+    }
+
   return FALSE;
 }
 
-/****************************************************************/
-/* Let's kick the whole thing off...                            */
-/****************************************************************/
+static void
+play_game (gpointer button, gpointer data)
+{
+  switch_state (STATE_PLAYING);
+}
 
-int
-main (gint argc,
-      gchar **argv)
+static void
+restart (gpointer button, gpointer data)
 {
-  int x, y;
+  if (current_state == STATE_EPILOGUE)
+    {
+      show_message ("Have patience while robotfindskitten.");
+    }
+  else
+    {
+      switch_state (STATE_PROLOGUE);
+    }
+}
 
-  gtk_init (&argc, &argv);
-  g_set_application_name ("robotfindskitten");
-  srandom (time(0));
+static void
+set_up_board (void)
+{
+  guint x, y;
+
+  if (current_state==STATE_PLAYING)
+    {
+      /* end of the game; clean up */
+
+      for (x=0; x < ARENA_WIDTH; x++)
+       for (y=0; y < ARENA_HEIGHT; y++)
+         if (arena[x][y])
+           {
+             gtk_container_remove (GTK_CONTAINER (state_widget[STATE_PLAYING]),
+                                   arena[x][y]);
+             arena[x][y] = NULL;
+           }
+
+      g_object_unref (robot);
+      g_object_unref (kitten);
+    }
+  else
+    {
+      /* make everything new */
+  
+      g_free (used);
+      used = g_malloc0 (nki_count * sizeof(gboolean));
 
-  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+      robot = gtk_label_new ("#");
+      g_object_ref (robot);
+      kitten = random_character ("You found kitten!  Way to go, robot!");
+      g_object_ref (kitten);
+
+      place_in_arena_randomly (robot);
+      place_in_arena_randomly (kitten);
+
+      if (nki_count < amount_of_random_stuff)
+       {
+         /* sanity check failed */
+         show_message ("There are too few non-kitten items to play a meaningful game.");
+         exit (EXIT_FAILURE);
+       }
+
+      for (x=0; x < amount_of_random_stuff; x++)
+       place_in_arena_randomly (random_character (description ()));
+
+      for (x=0; x < ARENA_WIDTH; x++)
+       for (y=0; y < ARENA_HEIGHT; y++)
+         if (!arena[x][y])
+           place_in_arena_at_xy (gtk_label_new (NULL), x, y);
+    }
+}
+
+static void
+set_up_widgets (void)
+{
+  GtkWidget *middle = gtk_hbox_new (FALSE, 0);
+  GtkWidget *buttons = gtk_hbox_new (TRUE, 0);
+  GtkWidget *explain = NULL, *button, *intro;
+  const char *explanation =
+    "In this game, you are robot (#). "
+    "Your job is to find kitten. This task is complicated "
+    "by the existence of various things which are not kitten. "
+    "Robot must touch items to determine if they are kitten or "
+    "not. The game ends when robotfindskitten. You may move "
+    "robot about by tapping on any side of robot, or with the "
+    "arrow keys.";
+  GKeyFile *desktop = g_key_file_new ();
+  gchar *version;
+  guint x, y;
+  HildonAppMenu *menu = HILDON_APP_MENU (hildon_app_menu_new ());
+  
+  /* The window */
+
+  window = hildon_window_new ();
   gtk_window_set_title (GTK_WINDOW (window), "robotfindskitten");
   gtk_widget_modify_bg (window, GTK_STATE_NORMAL, &black);
   g_signal_connect (G_OBJECT (window), "button-press-event", G_CALLBACK (on_window_clicked), NULL);
   g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (on_key_pressed), NULL);
   g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
-       
-  table = gtk_table_new (ARENA_HEIGHT, ARENA_WIDTH, TRUE);
-  gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (table));
 
-  robot = gtk_label_new ("#");
-  g_object_ref (robot);
-  kitten = random_character ("You found kitten!  Way to go, robot!");
+  /* The prologue */
+
+  /* Get the rather odd version string.  The RFK spec says that
+   * it should read v<major>.<minor>.<number-of-NKIs>.
+   */
+  if (g_key_file_load_from_file (desktop,
+                                "/usr/share/applications/hildon/rfk.desktop",
+                                G_KEY_FILE_NONE,
+                                NULL))
+    {
+      version = g_strdup_printf("v%s.%d",
+                               g_key_file_get_value (desktop, "Desktop Entry", "Version", NULL),
+                               nki_count);
+      g_key_file_free (desktop);
+    }
+  else
+    {
+      version = g_strdup("");
+    }
+
+  button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT,
+                                            HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
+                                            "Play", NULL);
+  g_signal_connect (button, "clicked", G_CALLBACK (play_game), NULL);
+
+  gtk_box_pack_end (GTK_BOX (buttons), button, TRUE, TRUE, 0);
+
+  button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_THUMB_HEIGHT,
+                                            HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
+                                            "Help", NULL);
+  g_signal_connect (button, "clicked", G_CALLBACK (get_help), NULL);
+  gtk_box_pack_end (GTK_BOX (buttons), button, TRUE, TRUE, 0);
+
+  /* and another help button, this time for the menu */
+  button = gtk_button_new_with_label ("Help");
+  g_signal_connect (button, "clicked", G_CALLBACK (get_help), NULL);
+  hildon_app_menu_append (menu, GTK_BUTTON (button));
+
+  button = gtk_button_new_with_label ("Restart");
+  g_signal_connect (button, "clicked", G_CALLBACK (restart), NULL);
+  hildon_app_menu_append (menu, GTK_BUTTON (button));
+
+  gtk_widget_show_all (GTK_WIDGET (menu));
+  hildon_window_set_app_menu (HILDON_WINDOW (window), menu);
 
-  place_in_arena_randomly (robot);
-  place_in_arena_randomly (kitten);
+  explain = gtk_label_new (explanation);
+  gtk_label_set_line_wrap (GTK_LABEL (explain), TRUE);
 
-  for (x=0; x < amount_of_random_stuff; x++)
-    place_in_arena_randomly (random_character (description ()));
+  gtk_box_pack_end (GTK_BOX (middle), explain, TRUE, TRUE, 0);
+  gtk_box_pack_end (GTK_BOX (middle), gtk_image_new_from_pixbuf (robot_pic), FALSE, FALSE, 0);
+
+  intro = gtk_vbox_new (FALSE, 0);
+  gtk_box_pack_end (GTK_BOX (intro), buttons, FALSE, FALSE, 0);
+  gtk_box_pack_end (GTK_BOX (intro), middle, TRUE, TRUE, 0);
+  gtk_box_pack_end (GTK_BOX (intro), gtk_label_new (version), FALSE, FALSE, 0);
+  g_free (version);
+
+  state_widget[STATE_PROLOGUE] = intro;
+
+  /* The game itself */
+
+  state_widget[STATE_PLAYING] = gtk_table_new (ARENA_HEIGHT, ARENA_WIDTH, TRUE);
+  g_signal_connect (state_widget[STATE_PLAYING], "parent-set", G_CALLBACK (set_up_board), NULL);
 
   for (x=0; x < ARENA_WIDTH; x++)
     for (y=0; y < ARENA_HEIGHT; y++)
-      if (!arena[x][y])
-       place_in_arena_at_xy (gtk_label_new (NULL), x, y);
+      arena[x][y] = NULL;
 
-  gtk_widget_show_all (window);
+  /* The epilogue */
+  state_widget[STATE_EPILOGUE] =  gtk_drawing_area_new ();
+  g_signal_connect (state_widget[STATE_EPILOGUE], "parent-set", G_CALLBACK (ending_animation), NULL);
+  g_signal_connect (G_OBJECT (state_widget[STATE_EPILOGUE]),
+                   "expose_event", G_CALLBACK (ending_animation_draw), NULL);
 
-  gdk_window_set_events (GTK_WIDGET (window)->window,
-                        gdk_window_get_events(GTK_WIDGET (window)->window) | GDK_BUTTON_PRESS_MASK);
+  for (x=0; x<STATE_LAST; x++)
+    {
+      /* so we don't lose them when we take them offscreen */
+      g_object_ref (state_widget[x]);
+    }
+}
+
+/****************************************************************/
+/* Let's kick the whole thing off...                            */
+/****************************************************************/
+
+int
+main (gint argc,
+      gchar **argv)
+{
+  gtk_init (&argc, &argv);
+  g_set_application_name ("robotfindskitten");
+  srandom (time(0));
 
-       
-  show_message (explanation);
+  ensure_messages_loaded ();
+  load_images ();
 
+  set_up_widgets ();
+  switch_state (STATE_PROLOGUE);
+  
   gtk_main ();
 
-  return 0;
+  return EXIT_SUCCESS;
 }