From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / pong.c
index 4941ae2803433ca00835dda526f719f970184cc0..fe200c35a65620d7c46e63f756dc4c44a615096f 100644 (file)
  * Also added gradual acceleration of the ball, shrinking of paddles, and
  * scorekeeping.
  *
+ * Modified by Gereon Steffens <gereon@steffens.org> to add -clock and -noise
+ * options. See http://www.burovormkrijgers.nl (ugly flash site, 
+ * navigate to Portfolio/Browse/Misc/Pong Clock) for the hardware implementation 
+ * that gave me the idea. In clock mode, the score reflects the current time, and 
+ * the paddles simply stop moving when it's time for the other side to score. This 
+ * means that the display is only updated a few seconds *after* the minute actually 
+ * changes, but I think this fuzzyness fits well with the display, and since we're
+ * not displaying seconds, who cares. While I was at it, I added a -noise option
+ * to control the noisyness of the display.
+ *
+ * Modified by Dave Odell <dmo2118@gmail.com> to add -p1 and -p2 options.
+ * JWXYZ doesn't support XWarpPointer, so PLAYER_MOUSE only works on native
+ * X11. JWXYZ also doesn't support cursors, so PLAYER_TABLET doesn't hide the
+ * mouse pointer.
+ *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  * the above copyright notice appear in all copies and that both that
 
 #include "screenhack.h"
 #include "analogtv.h"
+#include <time.h>
+#ifndef HAVE_JWXYZ
+# include <X11/keysym.h>
+#endif
 /* #define OUTPUT_POS */
 
+typedef enum {
+  PLAYER_AI,
+  PLAYER_MOUSE,
+  PLAYER_TABLET,
+  PLAYER_KEYBOARD,
+  PLAYER_KEYBOARD_LEFT
+} PlayerType;
+
 typedef struct _paddle {
+  PlayerType player;
   int x;
   int y;
   int w;
@@ -65,12 +93,15 @@ struct state {
   Display *dpy;
   Window window;
 
+  int clock;
+
   Paddle l_paddle;
   Paddle r_paddle;
   Ball ball;
   int bx,by;
   int m_unit;
   int paddle_rate;
+  double noise;
 
   analogtv *tv;
   analogtv_input *inp;
@@ -83,6 +114,18 @@ struct state {
   int net_ntsc[4];
 
   analogtv_font score_font;
+
+# ifndef HAVE_JWXYZ
+  Cursor null_cursor;
+# endif
+  int mouse_y;
+  unsigned w, h, screen_h, screen_h_mm;
+  Bool is_focused;
+  Bool key_w: 1;
+  Bool key_s: 1;
+  Bool key_up: 1;
+  Bool key_down: 1;
+  unsigned int dragging : 2;
 };
 
 
@@ -92,6 +135,9 @@ enum {
   PONG_TMARG = 10
 };
 
+static void
+p_hit_top_bottom(Paddle *p);
+
 static void
 hit_top_bottom(struct state *st)
 {
@@ -100,6 +146,25 @@ hit_top_bottom(struct state *st)
     st->by=-st->by;
 }
 
+static void
+reset_score(struct state * st)
+{
+  if (st->clock)
+  {
+    /* init score to current time */
+    time_t now = time(0);
+    struct tm* now_tm = localtime(&now);
+
+    st->r_paddle.score = now_tm->tm_hour;
+    st->l_paddle.score = now_tm->tm_min;
+  }
+  else
+  {
+    st->r_paddle.score = 0;
+    st->l_paddle.score = 0;
+  }
+}
+
 static void
 new_game(struct state *st)
 {
@@ -119,11 +184,14 @@ new_game(struct state *st)
   st->r_paddle.wait = 0;
   st->r_paddle.lock = 0;
   st->paddle_rate = st->m_unit-1;
-  st->r_paddle.score = 0;
-  st->l_paddle.score = 0;
+  reset_score(st);
 
   st->l_paddle.h = PONG_H/4;
   st->r_paddle.h = PONG_H/4;
+  /* Adjust paddle position again, because
+     paddle length is enlarged (reset) above. */
+  p_hit_top_bottom(&st->l_paddle);
+  p_hit_top_bottom(&st->r_paddle);
 }
 
 static void
@@ -167,11 +235,18 @@ hit_paddle(struct state *st)
         }
       else
         {
-          st->r_paddle.score++;
-          if (st->r_paddle.score >=10)
-                new_game(st);
-          else 
-          start_game(st);
+          if (st->clock)
+          {
+            reset_score(st);
+          }
+          else
+          {
+            st->r_paddle.score++;
+            if (st->r_paddle.score >=10)
+              new_game(st);
+            else 
+              start_game(st);
+          }
         }
     }
 
@@ -189,102 +264,321 @@ hit_paddle(struct state *st)
         }
       else
         {
-          st->l_paddle.score++;
-          if (st->l_paddle.score >= 10)
-                new_game(st);
+          if (st->clock)
+          {
+            reset_score(st);
+          }
           else
-          start_game(st);
+          {
+            st->l_paddle.score++;
+            if (st->l_paddle.score >= 10)
+              new_game(st);
+            else
+              start_game(st);
+          }
         }
     }
 }
 
+static PlayerType
+get_player_type(Display *dpy, char *rsrc)
+{
+  PlayerType result;
+  char *s = get_string_resource(dpy, rsrc, "String");
+  if (!strcmp(s, "ai") || !strcmp(s, "AI"))
+  {
+    result = PLAYER_AI;
+  }
+# ifndef HAVE_JWXYZ
+  else if (!strcmp(s, "mouse"))
+  {
+    result = PLAYER_MOUSE;
+  }
+# endif
+  else if (!strcmp(s, "tab") || !strcmp(s, "tablet"))
+  {
+    result = PLAYER_TABLET;
+  }
+  else if (!strcmp(s, "kb") || !strcmp(s, "keyb") || !strcmp(s, "keyboard") ||
+           !strcmp(s, "right") || !strcmp(s, "kbright") ||
+           !strcmp(s, "arrows"))
+  {
+    result = PLAYER_KEYBOARD;
+  }
+  else if (!strcmp(s, "left") || !strcmp(s, "kbleft") ||
+           !strcmp(s, "ws") || !strcmp(s, "wasd"))
+  {
+    result = PLAYER_KEYBOARD_LEFT;
+  }
+  else
+  {
+    fprintf(stderr, "%s: invalid player type\n", progname);
+    result = PLAYER_AI;
+  }
+  free(s);
+  return result;
+}
+
+static void
+do_shape (struct state *st, const XWindowAttributes *xgwa)
+{
+  st->w = xgwa->width;
+  st->h = xgwa->height;
+  st->screen_h = XHeightOfScreen(xgwa->screen);
+  st->screen_h_mm = XHeightMMOfScreen(xgwa->screen);
+}
+
+#ifndef HAVE_JWXYZ
+static Bool
+needs_grab (struct state *st)
+{
+  return
+  st->l_paddle.player == PLAYER_MOUSE ||
+  st->r_paddle.player == PLAYER_MOUSE;
+/*
+  st->l_paddle.player == PLAYER_TABLET ||
+  st->r_paddle.player == PLAYER_TABLET;
+ */
+}
+
+static void
+grab_pointer (struct state *st)
+{
+  st->is_focused = True;
+  XGrabPointer(st->dpy, st->window, True, PointerMotionMask, GrabModeAsync,
+               GrabModeAsync, st->window, st->null_cursor, CurrentTime);
+}
+#endif /* !HAVE_JWXYZ */
+
 static void *
 pong_init (Display *dpy, Window window)
 {
   struct state *st = (struct state *) calloc (1, sizeof(*st));
+
+  int i;
+  XWindowAttributes xgwa;
+  struct {
+    int w, h;
+    char *s[10];
+  } fonts[2] = { 
+    { /* regular pong font */
+      /* If you think we haven't learned anything since the early 70s,
+         look at this font for a while */
+      4, 6, 
+        { 
+            "****"
+            "*  *"
+            "*  *"
+            "*  *"
+            "*  *"
+            "****",
+
+            "   *"
+            "   *"
+            "   *"
+            "   *"
+            "   *"
+            "   *",
+
+            "****"
+            "   *"
+            "****"
+            "*   "
+            "*   "
+            "****",
+
+            "****"
+            "   *" 
+            "****"
+            "   *"
+            "   *"
+            "****",
+
+            "*  *"
+            "*  *"
+            "****"
+            "   *"
+            "   *"
+            "   *",
+
+            "****"
+            "*   "
+            "****"
+            "   *"
+            "   *"
+            "****",
+
+            "****"
+            "*   "
+            "****"
+            "*  *"
+            "*  *"
+            "****",
+
+            "****"
+            "   *"
+            "   *"
+            "   *"
+            "   *"
+            "   *",
+
+            "****"
+            "*  *"
+            "****"
+            "*  *"
+            "*  *"
+            "****",
+
+            "****"
+            "*  *"
+            "****"
+            "   *"
+            "   *"
+            "   *"
+        } 
+    },
+    { /* pong clock font - hand-crafted double size looks better */
+      8, 12, 
+        {
+            "####### "
+            "####### "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "####### "
+            "####### ",
+            
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   "
+            "   ##   ",
+
+            "####### "
+            "####### "
+            "     ## "
+            "     ## "
+            "####### "
+            "####### "
+            "##      "
+            "##      "
+            "##      "
+            "####### "
+            "####### ",
+
+            "####### "
+            "####### "
+            "     ## "
+            "     ## "
+            "####### "
+            "####### "
+            "     ## "
+            "     ## "
+            "     ## "
+            "####### "
+            "####### ",
+
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "####### "
+            "####### "
+            "     ## "
+            "     ## "
+            "     ## "
+            "     ## "
+            "     ## ",
+
+            "####### "
+            "####### "
+            "##      "
+            "##      "
+            "####### "
+            "####### "
+            "     ## "
+            "     ## "
+            "     ## "
+            "####### "
+            "####### ",
+
+            "####### "
+            "####### "
+            "##      "
+            "##      "
+            "####### "
+            "####### "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "####### "
+            "####### ",
+
+            "####### "
+            "####### "
+            "     ## "
+            "     ## "
+            "     ## "
+            "     ## "
+            "     ## "
+            "     ## "
+            "     ## "
+            "     ## " 
+            "     ## ",
+
+            "####### "
+            "####### "
+            "##   ## "
+            "##   ## "
+            "####### "
+            "####### "
+            "##   ## "
+            "##   ## "
+            "##   ## "
+            "####### "
+            "####### ",
+
+            "####### "
+            "####### "
+            "##   ## "
+            "##   ## "
+            "####### "
+            "####### "
+            "     ## "
+            "     ## "
+            "     ## "
+            "####### "
+            "####### "
+
+        }
+    }
+  };
+
   st->dpy = dpy;
   st->window = window;
   st->tv=analogtv_allocate(st->dpy, st->window);
   analogtv_set_defaults(st->tv, "");
 
-  analogtv_make_font(st->dpy, st->window, &st->score_font,
-                     4, 6, NULL );
-
-  /* If you think we haven't learned anything since the early 70s,
-     look at this font for a while */
-  analogtv_font_set_char(&st->score_font, '0',
-                        "****"
-                        "*  *"
-                        "*  *"
-                        "*  *"
-                        "*  *"
-                        "****");
-  analogtv_font_set_char(&st->score_font, '1',
-                        "   *"
-                        "   *"
-                        "   *"
-                        "   *"
-                        "   *"
-                        "   *");
-  analogtv_font_set_char(&st->score_font, '2',
-                        "****"
-                        "   *"
-                        "****"
-                        "*   "
-                        "*   "
-                        "****");
-  analogtv_font_set_char(&st->score_font, '3',
-                        "****"
-                        "   *"
-                        "****"
-                        "   *"
-                        "   *"
-                        "****");
-  analogtv_font_set_char(&st->score_font, '4',
-                        "*  *"
-                        "*  *"
-                        "****"
-                        "   *"
-                        "   *"
-                        "   *");
-  analogtv_font_set_char(&st->score_font, '5',
-                        "****"
-                        "*   "
-                        "****"
-                        "   *"
-                        "   *"
-                        "****");
-  analogtv_font_set_char(&st->score_font, '6',
-                        "****"
-                        "*   "
-                        "****"
-                        "*  *"
-                        "*  *"
-                        "****");
-  analogtv_font_set_char(&st->score_font, '7',
-                        "****"
-                        "   *"
-                        "   *"
-                        "   *"
-                        "   *"
-                        "   *");
-  analogtv_font_set_char(&st->score_font, '8',
-                        "****"
-                        "*  *"
-                        "****"
-                        "*  *"
-                        "*  *"
-                        "****");
-  analogtv_font_set_char(&st->score_font, '9',
-                        "****"
-                        "*  *"
-                        "****"
-                        "   *"
-                        "   *"
-                        "   *");
-
-  st->score_font.y_mult *= 2;
-  st->score_font.x_mult *= 2;
+
+  st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
+
+  analogtv_make_font(st->dpy, st->window, &st->score_font, 
+                     fonts[st->clock].w, fonts[st->clock].h, NULL);
+
+  for (i=0; i<10; ++i)
+  {
+    analogtv_font_set_char(&st->score_font, '0'+i, fonts[st->clock].s[i]);
+  }
 
 #ifdef OUTPUT_POS
   printf("screen(%d,%d,%d,%d)\n",0,0,PONG_W,PONG_H);
@@ -307,6 +601,7 @@ pong_init (Display *dpy, Window window)
 #endif
 
   /*Init the paddles*/
+  st->l_paddle.player = get_player_type(dpy, "p1");
   st->l_paddle.x = 8;
   st->l_paddle.y = 100;
   st->l_paddle.w = 16;
@@ -314,6 +609,7 @@ pong_init (Display *dpy, Window window)
   st->l_paddle.wait = 1;
   st->l_paddle.lock = 0;
   st->r_paddle = st->l_paddle;
+  st->r_paddle.player = get_player_type(dpy, "p2");
   st->r_paddle.x = PONG_W - 8 - st->r_paddle.w;
   st->r_paddle.wait = 0;
   /*Init the ball*/
@@ -322,7 +618,70 @@ pong_init (Display *dpy, Window window)
   st->ball.w = 16;
   st->ball.h = 8;
 
+  /* The mouse warping business breaks tablet input. */
+  if (st->l_paddle.player == PLAYER_MOUSE &&
+      st->r_paddle.player == PLAYER_TABLET)
+  {
+    st->l_paddle.player = PLAYER_TABLET;
+  }
+  if (st->r_paddle.player == PLAYER_MOUSE &&
+      st->l_paddle.player == PLAYER_TABLET)
+  {
+    st->r_paddle.player = PLAYER_TABLET;
+  }
+
+  if (st->clock) {
+    st->l_paddle.player = PLAYER_AI;
+    st->r_paddle.player = PLAYER_AI;
+    fprintf(stderr, "%s: clock mode requires AI control\n", progname);
+
+  }
+
+# ifndef HAVE_JWXYZ
+  if (st->l_paddle.player == PLAYER_MOUSE ||
+      st->r_paddle.player == PLAYER_MOUSE ||
+      st->l_paddle.player == PLAYER_TABLET ||
+      st->r_paddle.player == PLAYER_TABLET)
+  {
+    XColor black = {0, 0, 0, 0};
+    Pixmap cursor_pix = XCreatePixmap(dpy, window, 4, 4, 1);
+    XGCValues gcv;
+    GC mono_gc;
+
+    gcv.foreground = 0;
+    mono_gc = XCreateGC(dpy, cursor_pix, GCForeground, &gcv);
+    st->null_cursor = XCreatePixmapCursor(dpy, cursor_pix, cursor_pix,
+                                          &black, &black, 0, 0);
+    XFillRectangle(dpy, cursor_pix, mono_gc, 0, 0, 4, 4);
+    XFreeGC(dpy, mono_gc);
+
+    XSelectInput(dpy, window,
+                 PointerMotionMask | FocusChangeMask |
+                 KeyPressMask | KeyReleaseMask |
+                 ButtonPressMask | ButtonReleaseMask);
+
+    if (needs_grab(st))
+    {
+      grab_pointer(st);
+    }
+    else
+    {
+      XDefineCursor(dpy, window, st->null_cursor);
+    }
+  }
+# endif
+
   st->m_unit = get_integer_resource (st->dpy, "speed", "Integer");
+  st->noise  = get_float_resource(st->dpy, "noise", "Float");
+  st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
+
+  if (!st->clock)
+  {
+    st->score_font.y_mult *= 2;
+    st->score_font.x_mult *= 2;
+  }
+
+  reset_score(st);
 
   start_game(st);
 
@@ -337,41 +696,94 @@ pong_init (Display *dpy, Window window)
                       ANALOGTV_TOP, ANALOGTV_BOT,
                       st->field_ntsc);
 
+  XGetWindowAttributes(dpy, window, &xgwa);
+  do_shape(st, &xgwa);
+
   return st;
 }
 
 static void
 p_logic(struct state *st, Paddle *p)
 {
-  int targ;
-  if (st->bx > 0) {
-    targ = st->ball.y + st->by * (st->r_paddle.x-st->ball.x) / st->bx;
-  }
-  else if (st->bx < 0) {
-    targ = st->ball.y - st->by * (st->ball.x - st->l_paddle.x - st->l_paddle.w) / st->bx;
+  if (p->player == PLAYER_AI)
+  {
+    if (!p->wait)
+    {
+      int targ;
+      if (st->bx > 0) {
+        targ = st->ball.y + st->by * (st->r_paddle.x-st->ball.x) / st->bx;
+      }
+      else if (st->bx < 0) {
+        targ = st->ball.y - st->by * (st->ball.x - st->l_paddle.x - st->l_paddle.w) / st->bx;
+      }
+      else {
+        targ = st->ball.y;
+      }
+      if (targ > PONG_H) targ=PONG_H;
+      if (targ < 0) targ=0;
+
+      if (targ < p->y && !p->lock)
+      {
+        p->y -= st->paddle_rate;
+      }
+      else if (targ > (p->y + p->h) && !p->lock)
+      {
+        p->y += st->paddle_rate;
+      }
+      else
+      {
+        int move=targ - (p->y + p->h/2);
+        if (move>st->paddle_rate) move=st->paddle_rate;
+        if (move<-st->paddle_rate) move=-st->paddle_rate;
+        p->y += move;
+        p->lock = 1;
+      }
+    }
   }
-  else {
-    targ = st->ball.y;
+# ifndef HAVE_JWXYZ
+  else if (p->player == PLAYER_MOUSE)
+  {
+    /* Clipping happens elsewhere. */
+    /* As the screen resolution increases, the mouse moves faster in terms of
+       pixels, so divide by DPI. */
+    p->y += (int)(st->mouse_y - (st->h / 2)) * 4 * (int)st->screen_h_mm / (3 * (int)st->screen_h);
+    if (st->is_focused)
+      XWarpPointer (st->dpy, None, st->window, 0, 0, 0, 0, st->w / 2, st->h / 2);
   }
-  if (targ > PONG_H) targ=PONG_H;
-  if (targ < 0) targ=0;
-
-  if (targ < p->y && !p->lock)
+# endif
+  else if (p->player == PLAYER_TABLET)
   {
-    p->y -= st->paddle_rate;
+    p->y = st->mouse_y * (PONG_H - PONG_TMARG) / st->h + PONG_TMARG - p->h / 2;
   }
-  else if (targ > (p->y + p->h) && !p->lock)
+  else if (p->player == PLAYER_KEYBOARD)
   {
-    p->y += st->paddle_rate;
+    if (st->key_up)
+      p->y -= 8;
+    if (st->key_down)
+      p->y += 8;
   }
-  else
+  else if (p->player == PLAYER_KEYBOARD_LEFT)
   {
-    int move=targ - (p->y + p->h/2);
-    if (move>st->paddle_rate) move=st->paddle_rate;
-    if (move<-st->paddle_rate) move=-st->paddle_rate;
-    p->y += move;
-    p->lock = 1;
+    if (st->key_w)
+      p->y -= 8;
+    if (st->key_s)
+      p->y += 8;
   }
+
+  if ((st->dragging == 1 && p == &st->l_paddle) ||
+      (st->dragging == 2 && p == &st->r_paddle))
+    {
+      /* Not getting MotionNotify. */
+      Window root1, child1;
+      int mouse_x, mouse_y, root_x, root_y;
+      unsigned int mask;
+      if (XQueryPointer (st->dpy, st->window, &root1, &child1,
+                         &root_x, &root_y, &mouse_x, &mouse_y, &mask))
+        st->mouse_y = mouse_y;
+
+      if (st->mouse_y < 0) st->mouse_y = 0;
+      p->y = st->mouse_y * (PONG_H - PONG_TMARG) / st->h + PONG_TMARG - p->h / 2;
+    }
 }
 
 static void
@@ -442,17 +854,20 @@ paint_score(struct state *st)
 {
   char buf[256];
 
+  char* fmt = (st->clock ? "%02d" : "%d");
+
   analogtv_draw_solid(st->inp,
                       ANALOGTV_VIS_START, ANALOGTV_VIS_END,
                       ANALOGTV_TOP, ANALOGTV_TOP + 10+ st->score_font.char_h * st->score_font.y_mult,
                       st->field_ntsc);
 
-  sprintf(buf, "%d",st->r_paddle.score%256);
+
+  sprintf(buf, fmt ,st->r_paddle.score%256);
   analogtv_draw_string(st->inp, &st->score_font, buf,
                        ANALOGTV_VIS_START + 130, ANALOGTV_TOP + 8,
                        st->score_ntsc);
 
-  sprintf(buf, "%d",st->l_paddle.score%256);
+  sprintf(buf, fmt, st->l_paddle.score%256);
   analogtv_draw_string(st->inp, &st->score_font, buf,
                        ANALOGTV_VIS_END - 200, ANALOGTV_TOP + 8,
                        st->score_ntsc);
@@ -475,28 +890,59 @@ paint_net(struct state *st)
   }
 }
 
+static double
+double_time (void)
+{
+  struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+  struct timezone tzp;
+  gettimeofday(&now, &tzp);
+# else
+  gettimeofday(&now);
+# endif
+
+  return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
 static unsigned long
 pong_draw (Display *dpy, Window window, void *closure)
 {
   struct state *st = (struct state *) closure;
+  const analogtv_reception *reception = &st->reception;
+  double then = double_time(), now, timedelta;
+
+  if (st->clock)
+  {
+    time_t now = time(0);
+    struct tm* tm_now = localtime(&now);
+
+    if (st->r_paddle.score != tm_now->tm_hour)
+    {
+      /* l paddle must score */
+      st->r_paddle.wait = 1;
+    }
+    else if (st->l_paddle.score != tm_now->tm_min)
+    {
+      /* r paddle must score */
+      st->l_paddle.wait = 1;
+    }
+  }
   erase_ball(st);
 
   st->ball.x += st->bx;
   st->ball.y += st->by;
 
-  if ((random()%40)==0) {
-    if (st->bx>0) st->bx++; else st->bx--;
-  }
-
-  if (!st->r_paddle.wait)
+  if (!st->clock)
   {
-    p_logic(st, &st->r_paddle);
-  }
-  if (!st->l_paddle.wait)
-  {
-    p_logic(st, &st->l_paddle);
+    /* in non-clock mode, occasionally increase ball speed */
+    if ((random()%40)==0) {
+      if (st->bx>0) st->bx++; else st->bx--;
+    }
   }
 
+  p_logic(st, &st->r_paddle);
+  p_logic(st, &st->l_paddle);
+
   p_hit_top_bottom(&st->r_paddle);
   p_hit_top_bottom(&st->l_paddle);
 
@@ -517,12 +963,12 @@ pong_draw (Display *dpy, Window window, void *closure)
   }
   if (1) paint_ball(st);
 
-  analogtv_init_signal(st->tv, 0.04);
   analogtv_reception_update(&st->reception);
-  analogtv_add_signal(st->tv, &st->reception);
-  analogtv_draw(st->tv);
+  analogtv_draw(st->tv, st->noise, &reception, 1);
 
-  return 10000;
+  now = double_time();
+  timedelta = (1 / 29.97) - (now - then);
+  return timedelta > 0 ? timedelta * 1000000 : 0;
 }
 
 \f
@@ -531,13 +977,20 @@ static const char *pong_defaults [] = {
   ".background: black",
   ".foreground: white",
   "*speed:      6",
+  "*noise:      0.04",
+  "*clock:      false",
+  "*p1:         ai",
+  "*p2:         ai",
   ANALOGTV_DEFAULTS
-  "*TVContrast:      150",
   0
 };
 
 static XrmOptionDescRec pong_options [] = {
   { "-speed",           ".speed",     XrmoptionSepArg, 0 },
+  { "-noise",           ".noise",     XrmoptionSepArg, 0 },
+  { "-clock",           ".clock",     XrmoptionNoArg, "true" },
+  { "-p1",              ".p1",        XrmoptionSepArg, 0 },
+  { "-p2",              ".p2",        XrmoptionSepArg, 0 },
   ANALOGTV_OPTIONS
   { 0, 0, 0, 0 }
 };
@@ -547,12 +1000,110 @@ pong_reshape (Display *dpy, Window window, void *closure,
                  unsigned int w, unsigned int h)
 {
   struct state *st = (struct state *) closure;
+  XWindowAttributes xgwa;
   analogtv_reconfigure (st->tv);
+
+  XGetWindowAttributes(dpy, window, &xgwa); /* AnalogTV does this too. */
+  xgwa.width = w;
+  xgwa.height = h;
+  do_shape(st, &xgwa);
 }
 
 static Bool
 pong_event (Display *dpy, Window window, void *closure, XEvent *event)
 {
+  struct state *st = (struct state *) closure;
+  switch (event->type)
+  {
+  case MotionNotify:
+    st->mouse_y = event->xmotion.y;
+    break;
+# ifndef HAVE_JWXYZ
+  case FocusIn:
+    if (needs_grab(st))
+    {
+      grab_pointer(st);
+    }
+    break;
+  case FocusOut:
+    if (needs_grab(st))
+    {
+      XUngrabPointer (dpy, CurrentTime);
+      st->is_focused = False;
+    }
+    break;
+# endif /* !HAVE_JWXYZ */
+  case KeyPress:
+  case KeyRelease:
+    {
+      char c;
+      KeySym key;
+      XLookupString(&event->xkey, &c, 1, &key, 0);
+      Bool is_pressed = event->type == KeyPress;
+      switch(key)
+      {
+      case XK_Up:
+        if (st->l_paddle.player == PLAYER_KEYBOARD ||
+            st->r_paddle.player == PLAYER_KEYBOARD)
+          {
+            st->key_up = is_pressed;
+            return True;
+          }
+        break;
+      case XK_Down:
+        if (st->l_paddle.player == PLAYER_KEYBOARD ||
+            st->r_paddle.player == PLAYER_KEYBOARD)
+          {
+            st->key_down = is_pressed;
+            return True;
+          }
+        break;
+      case 'w':
+        if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT ||
+            st->r_paddle.player == PLAYER_KEYBOARD_LEFT)
+          {
+            st->key_w = is_pressed;
+            return True;
+          }
+        break;
+      case 's':
+        if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT ||
+            st->r_paddle.player == PLAYER_KEYBOARD_LEFT)
+          {
+            st->key_s = is_pressed;
+            return True;
+          }
+        break;
+      }
+    }
+
+  /* Allow the user to pick up and drag either paddle with the mouse,
+     even when not in a mouse-paddle mode. */
+
+  case ButtonPress:
+    if (st->dragging != 0)
+      return False;
+    else if (event->xbutton.x < st->w * 0.2)
+      {
+        if (st->l_paddle.player != PLAYER_MOUSE)
+          st->dragging = 1;
+        return True;
+      }
+    else if (event->xbutton.x > st->w * 0.8)
+      {
+        if (st->r_paddle.player != PLAYER_MOUSE)
+          st->dragging = 2;
+        return True;
+      }
+    break;
+  case ButtonRelease:
+    if (st->dragging != 0)
+      {
+        st->dragging = 0;
+        return True;
+      }
+    break;
+  }
   return False;
 }