[rfk-dev] Proposed extensions to HTTP to support remote kitten-finding

Martin Pool mbp@samba.org
Sat, 19 May 2001 13:24:12 +1000


--hK8Uo4Yp55NZU70L
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

  http://gnukeyring.sourceforge.net/cgi-bin/robotfindskitten.cgi

Comments very welcome.

diff -x CVS -x install-sh -x COPYING -x INSTALL -x aclocal.m4 -x config.* -=
x configure -x ltmain.sh -x missing -x mkinstalldirs -x texinfo.tex -N -x M=
akefile.in -x *~ -urpw robotfindskitten-cvs/configure.in robotfindskitten/c=
onfigure.in
--- robotfindskitten-cvs/configure.in	Sat Mar 10 21:30:04 2001
+++ robotfindskitten/configure.in	Sat May 19 12:56:45 2001
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 AC_INIT(src/draw.h)
-AM_INIT_AUTOMAKE(robotfindskitten,v1600005.248b)
+AM_INIT_AUTOMAKE(robotfindskitten,v1600005.248b-www)
 AM_CONFIG_HEADER(config.h)
 AC_LANG_C
=20
diff -x CVS -x install-sh -x COPYING -x INSTALL -x aclocal.m4 -x config.* -=
x configure -x ltmain.sh -x missing -x mkinstalldirs -x texinfo.tex -N -x M=
akefile.in -x *~ -urpw robotfindskitten-cvs/src/Makefile.am robotfindskitte=
n/src/Makefile.am
--- robotfindskitten-cvs/src/Makefile.am	Thu Feb 22 15:29:33 2001
+++ robotfindskitten/src/Makefile.am	Fri May 18 18:25:23 2001
@@ -4,3 +4,6 @@ execgames_PROGRAMS=3Drobotfindskitten
 robotfindskitten_SOURCES=3D draw.h messages.h robotfindskitten.c=20
 #robotfindskitten_LDADD=3D -lncurses=20
=20
+noinst_PROGRAMS=3Drobotfindskitten.cgi
+
+robotfindskitten_cgi_SOURCES=3D draw.h messages.h robotfindskitten.cgi.c r=
andom.c
diff -x CVS -x install-sh -x COPYING -x INSTALL -x aclocal.m4 -x config.* -=
x configure -x ltmain.sh -x missing -x mkinstalldirs -x texinfo.tex -N -x M=
akefile.in -x *~ -urpw robotfindskitten-cvs/src/colors.h robotfindskitten/s=
rc/colors.h
--- robotfindskitten-cvs/src/colors.h	Thu Jan  1 10:00:00 1970
+++ robotfindskitten/src/colors.h	Sat May 19 02:32:33 2001
@@ -0,0 +1,12 @@
+#define N_COLORS ((int) (sizeof colors / sizeof colors[0]))
+
+static char const *colors[] =3D
+{
+  "LightGrey", "Red", "Green", "Blue", "Yellow", "Cyan", "Purple", "White",
+  "Pink", "Coral", "Gold", "OliveGreen", "Violet", "Thistle", "Tan", "Salm=
on",
+  "Plum", "Orange", "Maroon", "Magenta", "SeaGreen", "LimeGreen", "Firebri=
ck",
+  "SteelBlue", "LightBlue", "CadetBlue", "Aquamarine", "YellowGreen", "Sea=
Green"
+};
+
+static char const *bg_color =3D "Black";
+static char const *fg_color =3D "LightGrey";
diff -x CVS -x install-sh -x COPYING -x INSTALL -x aclocal.m4 -x config.* -=
x configure -x ltmain.sh -x missing -x mkinstalldirs -x texinfo.tex -N -x M=
akefile.in -x *~ -urpw robotfindskitten-cvs/src/random.c robotfindskitten/s=
rc/random.c
--- robotfindskitten-cvs/src/random.c	Thu Jan  1 10:00:00 1970
+++ robotfindskitten/src/random.c	Fri May 18 23:50:30 2001
@@ -0,0 +1,65 @@
+/*
+ * Taken from the libuuid library, which is licensed under the GNU
+ * GPL.
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * Hacked up by Martin Pool in 2001 to assist robots finding kittens.
+ */
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "random.h"
+
+/*
+ * Generate a series of random bytes.  Use /dev/urandom if possible,
+ * and if not, use srandom/random.
+ */
+void get_random_bytes(void *buf, int nbytes)
+{
+    static int fd =3D -2;
+    int i;
+    char *cp =3D (char *) buf;
+
+    if (fd =3D=3D -2) {
+        fd =3D open("/dev/urandom", O_RDONLY);
+        srand((getpid() << 16) ^ getuid() ^ time(0));
+    }
+    if (fd >=3D 0) {
+        while (nbytes > 0) {
+            i =3D read(fd, cp, nbytes);
+            if (i < 0) {
+                if ((errno =3D=3D EINTR) || (errno =3D=3D EAGAIN))
+                    continue;
+                break;
+            }
+            nbytes -=3D i;
+            cp +=3D i;
+        }
+    }
+    if (nbytes =3D=3D 0)
+        return;
+
+    /* XXX put something better here if no /dev/random! */
+    for (i=3D0; i < nbytes; i++)
+        *cp++ =3D rand() & 0xFF;
+    return;
+=09
+}
+
+
+
+unsigned int get_random_int (void)
+{
+  unsigned int r;
+
+  get_random_bytes (&r, sizeof r);
+
+  return r;
+}
diff -x CVS -x install-sh -x COPYING -x INSTALL -x aclocal.m4 -x config.* -=
x configure -x ltmain.sh -x missing -x mkinstalldirs -x texinfo.tex -N -x M=
akefile.in -x *~ -urpw robotfindskitten-cvs/src/random.h robotfindskitten/s=
rc/random.h
--- robotfindskitten-cvs/src/random.h	Thu Jan  1 10:00:00 1970
+++ robotfindskitten/src/random.h	Fri May 18 23:50:35 2001
@@ -0,0 +1,2 @@
+unsigned int get_random_int (void);
+void get_random_bytes(void *buf, int nbytes);
diff -x CVS -x install-sh -x COPYING -x INSTALL -x aclocal.m4 -x config.* -=
x configure -x ltmain.sh -x missing -x mkinstalldirs -x texinfo.tex -N -x M=
akefile.in -x *~ -urpw robotfindskitten-cvs/src/robotfindskitten.cgi.c robo=
tfindskitten/src/robotfindskitten.cgi.c
--- robotfindskitten-cvs/src/robotfindskitten.cgi.c	Thu Jan  1 10:00:00 1970
+++ robotfindskitten/src/robotfindskitten.cgi.c	Sat May 19 13:03:11 2001
@@ -0,0 +1,663 @@
+/*
+ * robotfindskitten.cgi: A Zen-Themed Knick-Knack
+ *
+ * Copyright (C) 2001 by Martin Pool <mbp@sourcefrog.net>
+ * This program is dedicated to Stephane Miller.
+ *
+ * Original robotfindskitten, Copyright (C) 1997-2000 by Leonard Richardson
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License as
+ *   published by the Free Software Foundation; either version 2 of
+ *   the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or EXISTANCE OF KITTEN.  See the GNU General
+ *   Public License for more details.
+ *
+ *   http://www.gnu.org/copyleft/gpl.html
+ */
+
+/* This is the e-rfk, which runs as a CGI.
+=20
+   It keeps no state on the server -- everything is bounced to the
+   client and back again on every request, which makes it clean for
+   the server, but a little tricky to program.  Each instance of the
+   CGI lasts only for a single web request.  It's like juggling cute
+   little kittens.
+  =20
+   We want to be fairly paranoid about string parsing, since an
+   overflow in a CGI is considered harmful.
+
+   For extra marks, we really should gzip all the output if the
+   browser has indicated that it can accept that encoding. */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#if HAVE_CONFIG_H
+#include <config.h>
+static char* ver =3D VERSION;
+#endif
+
+#include "random.h"
+
+#include "messages.h"
+#include "colors.h"
+
+enum { FALSE =3D 0, TRUE =3D 1};
+
+
+/* An arena of classical dimensions.  */
+#define WIDTH  80
+#define HEIGHT 25
+
+/* This struct contains all the information we need to display an
+   object on the screen*/
+typedef struct
+{
+  int		color, bold;
+  unsigned char	character;
+  int           message;
+} screen_object;
+
+/* Constants for our internal representation of the screen.  nki's are
+   numbered from 2 upwards, matching entries in bogus[]. */
+#define EMPTY 0
+#define ROBOT 1
+#define KITTEN 2
+#define FIRST_NKI 3
+
+#define N_OBJECTS ((int) MESSAGES + FIRST_NKI)
+screen_object objects[N_OBJECTS];
+screen_object * const robot  =3D &objects[ROBOT];
+screen_object * const kitten =3D &objects[KITTEN];
+
+int robotx, roboty;
+
+/* If we can do compressed output, then this points to the compressor. */
+FILE *gz_out;
+ =20
+/* Screen array, indexed as [x][y].  Each value is EMPTY, ROBOT,
+   KITTEN, or an index into bogus[].  */
+int screen[WIDTH][HEIGHT];
+
+/* String representation of game state. */
+char packed_state[4 * WIDTH * HEIGHT + 1];
+
+char const *script_name;		/* =3D SCRIPT_NAME */
+
+static int randx (void)
+{
+  return get_random_int () % WIDTH;
+}
+
+static int randy (void)		/* i'm feeling randy! */
+{
+  return get_random_int () % HEIGHT;
+}
+
+static int randchar (void)
+{
+  /* Assume the other guy can handle 8bit.  We're not all american. */
+  return 32 + (get_random_int () % 96) + (get_random_int() & 0x80);
+}
+
+static int randcolor (void)
+{
+  return get_random_int() % N_COLORS;
+}
+
+static int randbold (void)
+{
+  return (get_random_int() % 2 ? TRUE:FALSE);
+}
+
+static void print_env (void)
+{
+  extern char **environ;
+  char **e;
+
+  for (e =3D environ; *e; e++)
+    {
+      // printf ("<BR>%s", *e);
+    }
+}
+
+static int validchar(char a)
+{
+  switch (a)
+    {
+    case '#':
+    case ' ':
+    case '_':
+    case 127:
+      return 0;
+    }
+  return 1;
+}
+
+
+/* Return TRUE if this cell can be directly reached from the current
+   robot position.
+
+   This is the most interesting part of translation to the web.
+   Moving a single step at a time is probably too painful.  Jumping
+   straight to objects is possibly too easy.  Moving in straight lines
+   looks funny on the screen.
+
+   Perhaps we should allow ~20 random moves at any point? */
+static int is_reachable (int x, int y)
+{
+#if defined(ROOK_MOVES)
+  if (x =3D=3D robotx)
+    {
+      /* in the same column.  is there anything between us? */
+      int delta =3D x > robotx ? -1 : +1;
+      int sx;
+      for (sx =3D x; sx !=3D robotx; sx +=3D delta)
+	if (screen[sx][y] !=3D EMPTY)
+	  return FALSE;
+      return TRUE;
+    }	 =20
+  else if (y =3D=3D roboty)
+    {
+      /* in the same row.  is there anything between us? */
+      int delta =3D y > roboty ? -1 : +1;
+      int sy;
+      for (sy =3D y; sy !=3D roboty; sy +=3D delta)
+	if (screen[x][sy] !=3D EMPTY)
+	  return FALSE;
+      return TRUE;
+    }
+  else
+    return FALSE;
+#elif defined(MOVE_ANYWHERE)
+  return TRUE;
+#else
+  return screen[x][y] !=3D EMPTY;
+#endif
+}
+
+
+static void draw_cell (int x, int y)
+{
+  int reachable;
+  screen_object const *o =3D NULL;
+ =20
+  if ((reachable =3D is_reachable(x, y)))
+    printf ("<A\n HREF=3D\"%s?x=3D%d&y=3D%d%s\">", script_name, x, y, pack=
ed_state);
+ =20
+  if (robotx =3D=3D x  &&  roboty =3D=3D y)
+    /* Robot hides whatever object is underneath */
+    o =3D robot;
+  else if (screen[x][y] > 0  &&  screen[x][y] < N_OBJECTS)
+    o =3D &objects[screen[x][y]];
+ =20
+  if (o)
+    {
+      //      fprintf (stderr, "character %c at %d, %d\n", o->character, x=
, y);
+      if (o =3D=3D robot)
+	fputs ("<BLINK>", stdout);
+      printf ("<FONT COLOR=3D\"%s\">", colors[o->color]);
+      if (o->bold)
+	fputs("<B>", stdout);
+      printf ("&#%d;", o->character);
+      if (o->bold)
+	fputs("</B>", stdout);
+      printf ("</FONT>");
+      if (o =3D=3D robot)
+	fputs ("</BLINK>", stdout);
+    }
+  else
+    {
+      printf ("&nbsp;");
+    }
+
+  if (reachable)
+    printf ("</A>");
+}
+
+
+static void send_arena (void)
+{
+  int        x, y;
+
+  for (y =3D 0; y < HEIGHT; y++)
+    {
+      printf ("\n<br>");
+      for (x =3D 0; x < WIDTH; x++)
+	draw_cell (x, y);
+    }
+}
+
+
+static void send_head (int hug_distance, int hug_kitten_char)
+{
+  char       host[60];
+  char const *a =3D "<A HREF=3D\"http://robotfindskitten.org\">",
+    *un_a =3D "</A>";
+
+  if (gethostname (host, sizeof host) =3D=3D -1)
+    host[0] =3D 0;
+  host[59] =3D 0;
+   =20
+  printf ("Content-Type: text/html\n");
+  if (gz_out)
+    printf ("Content-Encoding: gzip\n");
+  if (hug_kitten_char)=20
+    printf ("Refresh: 1;URL=3D%s?hug=3D%d&kitten=3D%x\n",
+	    script_name, hug_distance, hug_kitten_char);
+
+  printf ("\n");
+  fflush (stdout);
+
+  if (gz_out)
+    stdout =3D gz_out;
+ =20
+  printf ("<HTML><HEAD><TITLE>robotfindskitten %s", ver);
+  if (*host)
+    printf (" on %s", host);
+  printf ("</TITLE></HEAD>\n");
+  printf ("<BODY BGCOLOR=3D\"%s\" TEXT=3D\"%s\" LINK=3D\"%s\" VLINK=3D\"%s=
\"><TT>\n",
+	  bg_color, fg_color, fg_color, fg_color);
+  printf ("<H2>%sr%sobot%sf%sinds%sk%sitten ver %s</H2>",
+	  a, un_a, a, un_a, a, un_a, ver);
+}
+
+
+static void offer_restart (void)
+{
+  printf ("(<A HREF=3D\"%s\">restart</A>)<P>\n", script_name);
+}
+
+
+static void send_screen (void)
+{
+  send_arena ();
+ =20
+  printf ("</TT>\n");
+
+  print_env ();=20
+
+  printf ("</BODY>\n");
+  fflush (stdout);
+}
+
+
+static void send_instructions (void)
+{
+  printf ("<P>Web version (C) 2001 by "
+	  "<A HREF=3D\"http://sourcefrog.net/mbp/\">Martin Pool</A>,\n"
+	  "<BR>based on the original by the illustrious Leonard Richardson (C) 19=
97, 2000.\n"
+	  "<P>In this game, you are robot (<BLINK>#</BLINK>).  "
+	  "Your job is to find kitten.  This task\n"
+	  "is complicated by the existence of various things which are not kitten=
.  \n" =20
+	  "Robot must touch items to determine if they are kitten or not.  The ga=
me\n"
+	  "ends when robotfindskitten.  \n"
+	  "<P>Click to move around the board.\n"
+	  "<P>\n");
+}
+
+static void place_robot (int x, int y)
+{
+  robotx =3D x;
+  roboty =3D y;
+ =20
+  robot->character =3D '#';
+  robot->color =3D 0;
+  robot->bold =3D FALSE;
+
+  /* fprintf (stderr, "robot placed at (%d, %d)\n", robotx, roboty); */
+}
+
+
+
+/* Conduct Zen tuition for non-kitten objects.  (If we found the
+   kitten, hugging_kitten would have been called instead.) */
+static void show_found (void)
+{
+  int     s =3D screen[robotx][roboty];
+
+  if (s)
+    {
+      printf ("<b>%s</b>\n", messages[objects[s].message]);
+    }
+  printf ("<p>\n");
+}
+
+
+/* Game state is stored encoded as hex in the PATH_INFO section, and
+   transmitted in a slash after the name of the CGI.
+
+   Returns TRUE if everything was loaded successfully, otherwise FALSE
+   (in which case we must start a new game.)  */
+static int load_state (void)=20
+{
+  char const *q =3D getenv ("QUERY_STRING");
+  int		r;
+  int		x, y;
+
+  if (!q)
+    {
+      fprintf (stderr, "no query string -- a new game\n");
+      return FALSE;
+    }
+
+  r =3D sscanf (q, "x=3D%d&y=3D%d", &x, &y);
+  if (r !=3D 2)
+    {
+      fprintf (stderr, "didn't find robot -- starting a new game\n");
+      return FALSE;
+    }
+
+  q =3D strchr (q, '&');
+  while ((q =3D strchr (q + 1, '&')))
+    {
+      screen_object tmp_obj;
+      int           x, y, s;
+     =20
+      if (sscanf (q, "&c%x.%x.%x.%x.%x.%x.%hhx",
+		  &x, &y, &s, &tmp_obj.bold, &tmp_obj.color, &tmp_obj.message,
+		  &tmp_obj.character) !=3D 7)
+	{
+	  fprintf (stderr, "couldn't parse screen encoding at %s\n", q);
+	  return FALSE;
+	}
+
+      if (x < 0 || x >=3D WIDTH || y < 0 || y >=3D HEIGHT)
+	{
+	  fprintf (stderr, "coordinates (%d, %d) are bogus\n", x, y);
+	  return FALSE;
+	}
+
+      if (s < 1 || s >=3D N_OBJECTS)
+	{
+	  fprintf (stderr, "object index %d is bogus\n", s);
+	  return FALSE;
+	}
+
+      if (tmp_obj.color < 0 || tmp_obj.color >=3D N_COLORS)
+	{
+	  fprintf (stderr, "color %d is bogus\n", tmp_obj.color);
+	  return FALSE;
+	}
+
+      if (tmp_obj.message < 0 || tmp_obj.message >=3D MESSAGES)
+	{
+	  fprintf (stderr, "message index %d is bogus\n", tmp_obj.message);
+	  return FALSE;
+	}
+
+      screen[x][y] =3D s;
+      objects[s] =3D tmp_obj;
+    }
+
+  /*  fprintf (stderr, "found robot at (%d, %d)\n", x, y); */
+  place_robot (x, y);
+
+  return TRUE;
+}
+
+
+static void empty_screen (void)
+{
+  int x, y;
+
+  for (x =3D 0; x < WIDTH; x++)
+    for (y =3D 0; y < HEIGHT; y++)
+      screen[x][y] =3D EMPTY;
+}
+
+
+static void randomize_robot (void)
+{
+  int x, y;
+
+  do
+    {
+      x =3D randx ();
+      y =3D randy ();
+    }
+  while (screen[x][y] !=3D EMPTY);
+ =20
+  place_robot (x, y);
+}
+
+
+/*intialize kitten, well, intializes kitten.*/
+void randomize_kitten()
+{
+  int kx, ky;
+ =20
+  /* Assign the kitten a unique position. */
+  do
+    {
+      kx =3D randx();
+      ky =3D randy();
+    } while (screen[kx][ky]);
+ =20
+  /* Assign the kitten a character and a color. */
+  do {
+    kitten->character =3D randchar();
+  } while (!(validchar(kitten->character)));=20
+  screen[kx][ky] =3D KITTEN;
+
+  kitten->color =3D randcolor();
+  kitten->bold =3D randbold();
+
+  /*  fprintf (stderr, "kitten %c placed at (%d, %d)\n", kitten->character=
, kx, ky); */
+}
+
+
+void randomize_bogus (void)
+{
+  int num_bogus =3D 20;
+  int used_messages[MESSAGES];
+  int i;
+  screen_object *o =3D &objects[FIRST_NKI];
+  int which_msg;
+  int		x, y;
+  char const    *p;
+
+  p =3D getenv ("QUERY_STRING");
+  if ((p =3D strstr (p, "n=3D")))
+    sscanf (p, "n=3D%d", &num_bogus);
+ =20
+  bzero(used_messages, sizeof used_messages);
+
+  for (i =3D 0; i < num_bogus; i++)
+    {
+      o[i].color =3D randcolor();
+      o[i].bold =3D randbold();
+
+      do=20
+	o[i].character =3D randchar();
+      while (!(validchar(o[i].character)));
+
+      do
+	which_msg =3D get_random_int() % MESSAGES;
+      while (used_messages[which_msg]);
+
+      do
+	{
+	  x =3D randx(); y =3D randy();
+	}
+      while (screen[x][y] !=3D EMPTY);
+      screen[x][y] =3D FIRST_NKI + i;
+
+      used_messages[which_msg] =3D 1;
+      o[i].message =3D which_msg;
+
+      //      fprintf (stderr, "nki %d character %c at (%d, %d)\n", i, o[i=
].character, x, y);
+    }
+}
+
+
+static void randomize (void)
+{
+  randomize_kitten ();
+  randomize_bogus ();
+  randomize_robot ();
+}
+
+
+static void pack_state (void)
+{
+  int x, y;
+  char *p =3D packed_state;
+  int   s;
+
+  for (x =3D 0; x < WIDTH; x++)
+    for (y =3D 0; y < HEIGHT; y++)
+      {
+	s =3D screen[x][y];
+	/* Robots are not packed.  That would be cruel, and also leave
+           leftover robots all over the screen. */
+	if (s !=3D EMPTY  &&  s !=3D ROBOT)
+	  {
+	    int used;
+	    int remain =3D sizeof packed_state - (p - packed_state) - 1;
+	    screen_object const *o =3D &objects[s];
+
+	    used =3D snprintf(p, remain, "&c%x.%x.%x.%x.%x.%x.%x",
+			    x, y, s, o->bold, o->color, o->message, o->character);
+	    if (used > remain)
+	      {
+		fprintf (stderr, "not enough packed buffer space\n");
+		abort ();
+	      }
+	    p +=3D used;
+	  }
+      }
+
+  *p++ =3D 0;
+}
+
+
+static void check_for_gzip (void)
+{
+  char const *hdr =3D getenv("HTTP_ACCEPT_ENCODING");
+
+  gz_out =3D NULL;
+ =20
+  /* dodgy to just look for a substring, but it will always work */
+  if (strstr (hdr, "gzip"))
+    {
+      gz_out =3D popen ("gzip -c9", "w");
+      if (!gz_out)
+	{
+	  perror ("failed to start gzip");
+	  /* just continue without */
+	}
+    }
+}
+
+
+static void no_buffer (void)
+{
+  setvbuf (stdout, NULL, _IOLBF, 0);
+  setvbuf (stderr, NULL, _IOLBF, 0);
+}
+
+
+static void spaceout (int d)
+{
+  while (d-- > 0)
+    fputs ("&nbsp;", stdout);
+}
+
+
+/* Called to do the auto-refresh kitten hug.  We can get here either
+   because the robot just moved onto the kitten, or because we called
+   ourselves back through a Refresh to move the two friends closer
+   together. */
+static int hugging_kitten (void)
+{
+  int distance;
+  int kchar;
+  char const *p =3D getenv ("QUERY_STRING");
+
+  if (screen[robotx][roboty] =3D=3D KITTEN)
+    {
+      distance =3D 20;
+      kchar =3D objects[KITTEN].character;
+    }
+  else
+    {
+      if (!p)
+	return FALSE;
+
+      if (!(p =3D strstr (p, "hug=3D")))
+	return FALSE;
+ =20
+      if (sscanf (p, "hug=3D%d&kitten=3D%x", &distance, &kchar) !=3D 2)
+	return FALSE;
+    }
+
+  if (distance > 0)
+    {
+      send_head (distance-4, kchar);
+ =20
+      spaceout (40 - distance/2);
+      putc ('#', stdout);
+      spaceout (distance);
+      printf ("&#%d;", kchar);
+    }
+  else
+    {
+      send_head (0, 0);
+
+      printf ("<P><B>You found kitten!  Way to go, robot!</b>\n");
+      printf ("<P>");
+      offer_restart ();
+    }
+
+  return 1;
+}
+
+
+int main (void)			/* no arguments to CGI */
+{
+  int instruct =3D FALSE;
+ =20
+  script_name =3D getenv ("SCRIPT_NAME");
+
+  no_buffer ();
+  check_for_gzip ();
+
+  empty_screen ();
+
+  /* If we're already in the middle of a game, load in the state
+     arrays submitted from the browser.  If not, then we must be
+     starting a new game.  Set up the game state, and show the
+     starting screen. */
+  if (!load_state ())
+    {
+      randomize ();
+      instruct =3D TRUE;
+    }
+
+  if (hugging_kitten ())
+    return 0;
+ =20
+  /* must wait, because kitten-hugging manouever requires access to
+     HTTP headers. */
+  send_head (0, 0);
+  offer_restart ();
+  if (instruct)
+    send_instructions ();
+
+  show_found ();
+
+  fflush (stdout);
+  pack_state ();
+  fflush (stdout);
+  send_screen ();
+
+  return 0;
+}

--hK8Uo4Yp55NZU70L
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.5 (GNU/Linux)
Comment: For info see http://www.gnupg.org

iD8DBQE7BedbPGPKP6Cz6IsRAsrEAKCH7JcLkgrZdBj+wrFIfuZtKLNuEgCeLjTq
Gv2drhVpSFnAlj+Hg5ybB3c=
=ZAF9
-----END PGP SIGNATURE-----

--hK8Uo4Yp55NZU70L--