From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / pong.c
1 /* pong, Copyright (c) 2003 Jeremy English <jenglish@myself.com>
2  * A pong screen saver
3  *
4  * Modified by Brian Sawicki <sawicki@imsa.edu> to fix a small bug.
5  * Before this fix after a certain point the paddles would be too
6  * small for the program to effectively hit the ball.  The score would
7  * then skyrocket as the paddles missed most every time. Added a max
8  * so that once a paddle gets 10 the entire game restarts.  Special
9  * thanks to Scott Zager for some help on this.
10  *
11  * Modified by Trevor Blackwell <tlb@tlb.org> to use analogtv.[ch] display.
12  * Also added gradual acceleration of the ball, shrinking of paddles, and
13  * scorekeeping.
14  *
15  * Modified by Gereon Steffens <gereon@steffens.org> to add -clock and -noise
16  * options. See http://www.burovormkrijgers.nl (ugly flash site, 
17  * navigate to Portfolio/Browse/Misc/Pong Clock) for the hardware implementation 
18  * that gave me the idea. In clock mode, the score reflects the current time, and 
19  * the paddles simply stop moving when it's time for the other side to score. This 
20  * means that the display is only updated a few seconds *after* the minute actually 
21  * changes, but I think this fuzzyness fits well with the display, and since we're
22  * not displaying seconds, who cares. While I was at it, I added a -noise option
23  * to control the noisyness of the display.
24  *
25  * Modified by Dave Odell <dmo2118@gmail.com> to add -p1 and -p2 options.
26  * JWXYZ doesn't support XWarpPointer, so PLAYER_MOUSE only works on native
27  * X11. JWXYZ also doesn't support cursors, so PLAYER_TABLET doesn't hide the
28  * mouse pointer.
29  *
30  * Permission to use, copy, modify, distribute, and sell this software and its
31  * documentation for any purpose is hereby granted without fee, provided that
32  * the above copyright notice appear in all copies and that both that
33  * copyright notice and this permission notice appear in supporting
34  * documentation.  No representations are made about the suitability of this
35  * software for any purpose.  It is provided "as is" without express or
36  * implied warranty.
37  */
38
39 /*
40  * TLB sez: I haven't actually seen a pong game since I was about 9. Can
41  * someone who has one make this look more realistic? Issues:
42  *
43  *  - the font for scores is wrong. For example '0' was square.
44  *  - was there some kind of screen display when someone won?
45  *  - did the ball move smoothly, or was the X or Y position quantized?
46  *
47  * It could also use better player logic: moving the paddle even when the ball
48  * is going away, and making mistakes instead of just not keeping up with the
49  * speeding ball.
50  *
51  * There is some info at http://www.mameworld.net/discrete/Atari/Atari.htm#Pong
52  *
53  * It says that the original Pong game did not have a microprocessor, or even a
54  * custom integrated circuit. It was all discrete logic.
55  *
56  */
57
58 #include "screenhack.h"
59 #include "analogtv.h"
60 #include <time.h>
61 #ifndef HAVE_JWXYZ
62 # include <X11/keysym.h>
63 #endif
64 /* #define OUTPUT_POS */
65
66 typedef enum {
67   PLAYER_AI,
68   PLAYER_MOUSE,
69   PLAYER_TABLET,
70   PLAYER_KEYBOARD,
71   PLAYER_KEYBOARD_LEFT
72 } PlayerType;
73
74 typedef struct _paddle {
75   PlayerType player;
76   int x;
77   int y;
78   int w;
79   int h;
80   int wait;
81   int lock;
82   int score;
83 } Paddle;
84
85 typedef struct _ball {
86   int x;
87   int y;
88   int w;
89   int h;
90 } Ball;
91
92 struct state {
93   Display *dpy;
94   Window window;
95
96   int clock;
97
98   Paddle l_paddle;
99   Paddle r_paddle;
100   Ball ball;
101   int bx,by;
102   int m_unit;
103   int paddle_rate;
104   double noise;
105
106   analogtv *tv;
107   analogtv_input *inp;
108   analogtv_reception reception;
109
110   int paddle_ntsc[4];
111   int field_ntsc[4];
112   int ball_ntsc[4];
113   int score_ntsc[4];
114   int net_ntsc[4];
115
116   analogtv_font score_font;
117
118 # ifndef HAVE_JWXYZ
119   Cursor null_cursor;
120 # endif
121   int mouse_y;
122   unsigned w, h, screen_h, screen_h_mm;
123   Bool is_focused;
124   Bool key_w: 1;
125   Bool key_s: 1;
126   Bool key_up: 1;
127   Bool key_down: 1;
128   unsigned int dragging : 2;
129 };
130
131
132 enum {
133   PONG_W = ANALOGTV_VIS_LEN,
134   PONG_H = ANALOGTV_VISLINES,
135   PONG_TMARG = 10
136 };
137
138 static void
139 p_hit_top_bottom(Paddle *p);
140
141 static void
142 hit_top_bottom(struct state *st)
143 {
144   if ( (st->ball.y <= PONG_TMARG) ||
145        (st->ball.y+st->ball.h >= PONG_H) )
146     st->by=-st->by;
147 }
148
149 static void
150 reset_score(struct state * st)
151 {
152   if (st->clock)
153   {
154     /* init score to current time */
155     time_t now = time(0);
156     struct tm* now_tm = localtime(&now);
157
158     st->r_paddle.score = now_tm->tm_hour;
159     st->l_paddle.score = now_tm->tm_min;
160   }
161   else
162   {
163     st->r_paddle.score = 0;
164     st->l_paddle.score = 0;
165   }
166 }
167
168 static void
169 new_game(struct state *st)
170 {
171   /* Starts a Whole New Game*/
172   st->ball.x = PONG_W/2;
173   st->ball.y = PONG_H/2;
174   st->bx = st->m_unit;
175   st->by = st->m_unit;
176
177   /* jwz: perhaps not totally accurate, but randomize things a little bit
178      so that games on multiple screens are not identical. */
179   if (random() & 1) st->by = -st->by;
180   st->ball.y += (random() % (PONG_H/6))-(PONG_H/3);
181
182   st->l_paddle.wait = 1;
183   st->l_paddle.lock = 0;
184   st->r_paddle.wait = 0;
185   st->r_paddle.lock = 0;
186   st->paddle_rate = st->m_unit-1;
187   reset_score(st);
188
189   st->l_paddle.h = PONG_H/4;
190   st->r_paddle.h = PONG_H/4;
191   /* Adjust paddle position again, because
192      paddle length is enlarged (reset) above. */
193   p_hit_top_bottom(&st->l_paddle);
194   p_hit_top_bottom(&st->r_paddle);
195 }
196
197 static void
198 start_game(struct state *st)
199 {
200   /*Init the ball*/
201   st->ball.x = PONG_W/2;
202   st->ball.y = PONG_H/2;
203   st->bx = st->m_unit;
204   st->by = st->m_unit;
205
206   /* jwz: perhaps not totally accurate, but randomize things a little bit
207      so that games on multiple screens are not identical. */
208   if (random() & 1) st->by = -st->by;
209   st->ball.y += (random() % (PONG_H/6))-(PONG_H/3);
210
211   st->l_paddle.wait = 1;
212   st->l_paddle.lock = 0;
213   st->r_paddle.wait = 0;
214   st->r_paddle.lock = 0;
215   st->paddle_rate = st->m_unit-1;
216
217   if (st->l_paddle.h > 10) st->l_paddle.h= st->l_paddle.h*19/20;
218   if (st->r_paddle.h > 10) st->r_paddle.h= st->r_paddle.h*19/20;
219 }
220
221 static void
222 hit_paddle(struct state *st)
223 {
224   if ( st->ball.x + st->ball.w >= st->r_paddle.x &&
225        st->bx > 0 ) /*we are traveling to the right*/
226     {
227       if ((st->ball.y + st->ball.h > st->r_paddle.y) &&
228           (st->ball.y < st->r_paddle.y + st->r_paddle.h))
229         {
230           st->bx=-st->bx;
231           st->l_paddle.wait = 0;
232           st->r_paddle.wait = 1;
233           st->r_paddle.lock = 0;
234           st->l_paddle.lock = 0;
235         }
236       else
237         {
238           if (st->clock)
239           {
240             reset_score(st);
241           }
242           else
243           {
244             st->r_paddle.score++;
245             if (st->r_paddle.score >=10)
246               new_game(st);
247             else 
248               start_game(st);
249           }
250         }
251     }
252
253   if (st->ball.x <= st->l_paddle.x + st->l_paddle.w &&
254       st->bx < 0 ) /*we are traveling to the left*/
255     {
256       if ( st->ball.y + st->ball.h > st->l_paddle.y &&
257            st->ball.y < st->l_paddle.y + st->l_paddle.h)
258         {
259           st->bx=-st->bx;
260           st->l_paddle.wait = 1;
261           st->r_paddle.wait = 0;
262           st->r_paddle.lock = 0;
263           st->l_paddle.lock = 0;
264         }
265       else
266         {
267           if (st->clock)
268           {
269             reset_score(st);
270           }
271           else
272           {
273             st->l_paddle.score++;
274             if (st->l_paddle.score >= 10)
275               new_game(st);
276             else
277               start_game(st);
278           }
279         }
280     }
281 }
282
283 static PlayerType
284 get_player_type(Display *dpy, char *rsrc)
285 {
286   PlayerType result;
287   char *s = get_string_resource(dpy, rsrc, "String");
288   if (!strcmp(s, "ai") || !strcmp(s, "AI"))
289   {
290     result = PLAYER_AI;
291   }
292 # ifndef HAVE_JWXYZ
293   else if (!strcmp(s, "mouse"))
294   {
295     result = PLAYER_MOUSE;
296   }
297 # endif
298   else if (!strcmp(s, "tab") || !strcmp(s, "tablet"))
299   {
300     result = PLAYER_TABLET;
301   }
302   else if (!strcmp(s, "kb") || !strcmp(s, "keyb") || !strcmp(s, "keyboard") ||
303            !strcmp(s, "right") || !strcmp(s, "kbright") ||
304            !strcmp(s, "arrows"))
305   {
306     result = PLAYER_KEYBOARD;
307   }
308   else if (!strcmp(s, "left") || !strcmp(s, "kbleft") ||
309            !strcmp(s, "ws") || !strcmp(s, "wasd"))
310   {
311     result = PLAYER_KEYBOARD_LEFT;
312   }
313   else
314   {
315     fprintf(stderr, "%s: invalid player type\n", progname);
316     result = PLAYER_AI;
317   }
318   free(s);
319   return result;
320 }
321
322 static void
323 do_shape (struct state *st, const XWindowAttributes *xgwa)
324 {
325   st->w = xgwa->width;
326   st->h = xgwa->height;
327   st->screen_h = XHeightOfScreen(xgwa->screen);
328   st->screen_h_mm = XHeightMMOfScreen(xgwa->screen);
329 }
330
331 #ifndef HAVE_JWXYZ
332 static Bool
333 needs_grab (struct state *st)
334 {
335   return
336   st->l_paddle.player == PLAYER_MOUSE ||
337   st->r_paddle.player == PLAYER_MOUSE;
338 /*
339   st->l_paddle.player == PLAYER_TABLET ||
340   st->r_paddle.player == PLAYER_TABLET;
341  */
342 }
343
344 static void
345 grab_pointer (struct state *st)
346 {
347   st->is_focused = True;
348   XGrabPointer(st->dpy, st->window, True, PointerMotionMask, GrabModeAsync,
349                GrabModeAsync, st->window, st->null_cursor, CurrentTime);
350 }
351 #endif /* !HAVE_JWXYZ */
352
353 static void *
354 pong_init (Display *dpy, Window window)
355 {
356   struct state *st = (struct state *) calloc (1, sizeof(*st));
357
358   int i;
359   XWindowAttributes xgwa;
360   struct {
361     int w, h;
362     char *s[10];
363   } fonts[2] = { 
364     { /* regular pong font */
365       /* If you think we haven't learned anything since the early 70s,
366          look at this font for a while */
367       4, 6, 
368         { 
369             "****"
370             "*  *"
371             "*  *"
372             "*  *"
373             "*  *"
374             "****",
375
376             "   *"
377             "   *"
378             "   *"
379             "   *"
380             "   *"
381             "   *",
382
383             "****"
384             "   *"
385             "****"
386             "*   "
387             "*   "
388             "****",
389
390             "****"
391             "   *" 
392             "****"
393             "   *"
394             "   *"
395             "****",
396
397             "*  *"
398             "*  *"
399             "****"
400             "   *"
401             "   *"
402             "   *",
403
404             "****"
405             "*   "
406             "****"
407             "   *"
408             "   *"
409             "****",
410
411             "****"
412             "*   "
413             "****"
414             "*  *"
415             "*  *"
416             "****",
417
418             "****"
419             "   *"
420             "   *"
421             "   *"
422             "   *"
423             "   *",
424
425             "****"
426             "*  *"
427             "****"
428             "*  *"
429             "*  *"
430             "****",
431
432             "****"
433             "*  *"
434             "****"
435             "   *"
436             "   *"
437             "   *"
438         } 
439     },
440     { /* pong clock font - hand-crafted double size looks better */
441       8, 12, 
442         {
443             "####### "
444             "####### "
445             "##   ## "
446             "##   ## "
447             "##   ## "
448             "##   ## "
449             "##   ## "
450             "##   ## "
451             "##   ## "
452             "####### "
453             "####### ",
454             
455             "   ##   "
456             "   ##   "
457             "   ##   "
458             "   ##   "
459             "   ##   "
460             "   ##   "
461             "   ##   "
462             "   ##   "
463             "   ##   "
464             "   ##   "
465             "   ##   ",
466
467             "####### "
468             "####### "
469             "     ## "
470             "     ## "
471             "####### "
472             "####### "
473             "##      "
474             "##      "
475             "##      "
476             "####### "
477             "####### ",
478
479             "####### "
480             "####### "
481             "     ## "
482             "     ## "
483             "####### "
484             "####### "
485             "     ## "
486             "     ## "
487             "     ## "
488             "####### "
489             "####### ",
490
491             "##   ## "
492             "##   ## "
493             "##   ## "
494             "##   ## "
495             "####### "
496             "####### "
497             "     ## "
498             "     ## "
499             "     ## "
500             "     ## "
501             "     ## ",
502
503             "####### "
504             "####### "
505             "##      "
506             "##      "
507             "####### "
508             "####### "
509             "     ## "
510             "     ## "
511             "     ## "
512             "####### "
513             "####### ",
514
515             "####### "
516             "####### "
517             "##      "
518             "##      "
519             "####### "
520             "####### "
521             "##   ## "
522             "##   ## "
523             "##   ## "
524             "####### "
525             "####### ",
526
527             "####### "
528             "####### "
529             "     ## "
530             "     ## "
531             "     ## "
532             "     ## "
533             "     ## "
534             "     ## "
535             "     ## "
536             "     ## " 
537             "     ## ",
538
539             "####### "
540             "####### "
541             "##   ## "
542             "##   ## "
543             "####### "
544             "####### "
545             "##   ## "
546             "##   ## "
547             "##   ## "
548             "####### "
549             "####### ",
550
551             "####### "
552             "####### "
553             "##   ## "
554             "##   ## "
555             "####### "
556             "####### "
557             "     ## "
558             "     ## "
559             "     ## "
560             "####### "
561             "####### "
562
563         }
564     }
565   };
566
567   st->dpy = dpy;
568   st->window = window;
569   st->tv=analogtv_allocate(st->dpy, st->window);
570   analogtv_set_defaults(st->tv, "");
571
572
573   st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
574
575   analogtv_make_font(st->dpy, st->window, &st->score_font, 
576                      fonts[st->clock].w, fonts[st->clock].h, NULL);
577
578   for (i=0; i<10; ++i)
579   {
580     analogtv_font_set_char(&st->score_font, '0'+i, fonts[st->clock].s[i]);
581   }
582
583 #ifdef OUTPUT_POS
584   printf("screen(%d,%d,%d,%d)\n",0,0,PONG_W,PONG_H);
585 #endif
586
587   st->inp=analogtv_input_allocate();
588   analogtv_setup_sync(st->inp, 0, 0);
589
590   st->reception.input = st->inp;
591   st->reception.level = 2.0;
592   st->reception.ofs=0;
593 #if 0
594   if (random()) {
595     st->reception.multipath = frand(1.0);
596   } else {
597 #endif
598     st->reception.multipath=0.0;
599 #if 0
600   }
601 #endif
602
603   /*Init the paddles*/
604   st->l_paddle.player = get_player_type(dpy, "p1");
605   st->l_paddle.x = 8;
606   st->l_paddle.y = 100;
607   st->l_paddle.w = 16;
608   st->l_paddle.h = PONG_H/4;
609   st->l_paddle.wait = 1;
610   st->l_paddle.lock = 0;
611   st->r_paddle = st->l_paddle;
612   st->r_paddle.player = get_player_type(dpy, "p2");
613   st->r_paddle.x = PONG_W - 8 - st->r_paddle.w;
614   st->r_paddle.wait = 0;
615   /*Init the ball*/
616   st->ball.x = PONG_W/2;
617   st->ball.y = PONG_H/2;
618   st->ball.w = 16;
619   st->ball.h = 8;
620
621   /* The mouse warping business breaks tablet input. */
622   if (st->l_paddle.player == PLAYER_MOUSE &&
623       st->r_paddle.player == PLAYER_TABLET)
624   {
625     st->l_paddle.player = PLAYER_TABLET;
626   }
627   if (st->r_paddle.player == PLAYER_MOUSE &&
628       st->l_paddle.player == PLAYER_TABLET)
629   {
630     st->r_paddle.player = PLAYER_TABLET;
631   }
632
633   if (st->clock) {
634     st->l_paddle.player = PLAYER_AI;
635     st->r_paddle.player = PLAYER_AI;
636     fprintf(stderr, "%s: clock mode requires AI control\n", progname);
637
638   }
639
640 # ifndef HAVE_JWXYZ
641   if (st->l_paddle.player == PLAYER_MOUSE ||
642       st->r_paddle.player == PLAYER_MOUSE ||
643       st->l_paddle.player == PLAYER_TABLET ||
644       st->r_paddle.player == PLAYER_TABLET)
645   {
646     XColor black = {0, 0, 0, 0};
647     Pixmap cursor_pix = XCreatePixmap(dpy, window, 4, 4, 1);
648     XGCValues gcv;
649     GC mono_gc;
650
651     gcv.foreground = 0;
652     mono_gc = XCreateGC(dpy, cursor_pix, GCForeground, &gcv);
653     st->null_cursor = XCreatePixmapCursor(dpy, cursor_pix, cursor_pix,
654                                           &black, &black, 0, 0);
655     XFillRectangle(dpy, cursor_pix, mono_gc, 0, 0, 4, 4);
656     XFreeGC(dpy, mono_gc);
657
658     XSelectInput(dpy, window,
659                  PointerMotionMask | FocusChangeMask |
660                  KeyPressMask | KeyReleaseMask |
661                  ButtonPressMask | ButtonReleaseMask);
662
663     if (needs_grab(st))
664     {
665       grab_pointer(st);
666     }
667     else
668     {
669       XDefineCursor(dpy, window, st->null_cursor);
670     }
671   }
672 # endif
673
674   st->m_unit = get_integer_resource (st->dpy, "speed", "Integer");
675   st->noise  = get_float_resource(st->dpy, "noise", "Float");
676   st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
677
678   if (!st->clock)
679   {
680     st->score_font.y_mult *= 2;
681     st->score_font.x_mult *= 2;
682   }
683
684   reset_score(st);
685
686   start_game(st);
687
688   analogtv_lcp_to_ntsc(ANALOGTV_BLACK_LEVEL, 0.0, 0.0, st->field_ntsc);
689   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->ball_ntsc);
690   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->paddle_ntsc);
691   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->score_ntsc);
692   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->net_ntsc);
693
694   analogtv_draw_solid(st->inp,
695                       ANALOGTV_VIS_START, ANALOGTV_VIS_END,
696                       ANALOGTV_TOP, ANALOGTV_BOT,
697                       st->field_ntsc);
698
699   XGetWindowAttributes(dpy, window, &xgwa);
700   do_shape(st, &xgwa);
701
702   return st;
703 }
704
705 static void
706 p_logic(struct state *st, Paddle *p)
707 {
708   if (p->player == PLAYER_AI)
709   {
710     if (!p->wait)
711     {
712       int targ;
713       if (st->bx > 0) {
714         targ = st->ball.y + st->by * (st->r_paddle.x-st->ball.x) / st->bx;
715       }
716       else if (st->bx < 0) {
717         targ = st->ball.y - st->by * (st->ball.x - st->l_paddle.x - st->l_paddle.w) / st->bx;
718       }
719       else {
720         targ = st->ball.y;
721       }
722       if (targ > PONG_H) targ=PONG_H;
723       if (targ < 0) targ=0;
724
725       if (targ < p->y && !p->lock)
726       {
727         p->y -= st->paddle_rate;
728       }
729       else if (targ > (p->y + p->h) && !p->lock)
730       {
731         p->y += st->paddle_rate;
732       }
733       else
734       {
735         int move=targ - (p->y + p->h/2);
736         if (move>st->paddle_rate) move=st->paddle_rate;
737         if (move<-st->paddle_rate) move=-st->paddle_rate;
738         p->y += move;
739         p->lock = 1;
740       }
741     }
742   }
743 # ifndef HAVE_JWXYZ
744   else if (p->player == PLAYER_MOUSE)
745   {
746     /* Clipping happens elsewhere. */
747     /* As the screen resolution increases, the mouse moves faster in terms of
748        pixels, so divide by DPI. */
749     p->y += (int)(st->mouse_y - (st->h / 2)) * 4 * (int)st->screen_h_mm / (3 * (int)st->screen_h);
750     if (st->is_focused)
751       XWarpPointer (st->dpy, None, st->window, 0, 0, 0, 0, st->w / 2, st->h / 2);
752   }
753 # endif
754   else if (p->player == PLAYER_TABLET)
755   {
756     p->y = st->mouse_y * (PONG_H - PONG_TMARG) / st->h + PONG_TMARG - p->h / 2;
757   }
758   else if (p->player == PLAYER_KEYBOARD)
759   {
760     if (st->key_up)
761       p->y -= 8;
762     if (st->key_down)
763       p->y += 8;
764   }
765   else if (p->player == PLAYER_KEYBOARD_LEFT)
766   {
767     if (st->key_w)
768       p->y -= 8;
769     if (st->key_s)
770       p->y += 8;
771   }
772
773   if ((st->dragging == 1 && p == &st->l_paddle) ||
774       (st->dragging == 2 && p == &st->r_paddle))
775     {
776       /* Not getting MotionNotify. */
777       Window root1, child1;
778       int mouse_x, mouse_y, root_x, root_y;
779       unsigned int mask;
780       if (XQueryPointer (st->dpy, st->window, &root1, &child1,
781                          &root_x, &root_y, &mouse_x, &mouse_y, &mask))
782         st->mouse_y = mouse_y;
783
784       if (st->mouse_y < 0) st->mouse_y = 0;
785       p->y = st->mouse_y * (PONG_H - PONG_TMARG) / st->h + PONG_TMARG - p->h / 2;
786     }
787 }
788
789 static void
790 p_hit_top_bottom(Paddle *p)
791 {
792   if(p->y <= PONG_TMARG)
793   {
794     p->y = PONG_TMARG;
795   }
796   if((p->y + p->h) >= PONG_H)
797   {
798     p->y = PONG_H - p->h;
799   }
800 }
801
802 /*
803   XFillRectangle (dpy, window, gc, p->x, p->y, p->w, p->h);
804   if (old_v > p->y)
805   {
806     XClearArea(dpy,window, p->x, p->y + p->h,
807       p->w, (old_v + p->h) - (p->y + p->h), 0);
808   }
809   else if (old_v < p->y)
810   {
811     XClearArea(dpy,window, p->x, old_v, p->w, p->y - old_v, 0);
812   }
813 */
814 static void
815 paint_paddle(struct state *st, Paddle *p)
816 {
817   analogtv_draw_solid(st->inp,
818                       ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w,
819                       ANALOGTV_TOP, ANALOGTV_BOT,
820                       st->field_ntsc);
821
822   analogtv_draw_solid(st->inp,
823                       ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w,
824                       ANALOGTV_TOP + p->y, ANALOGTV_TOP + p->y + p->h,
825                       st->paddle_ntsc);
826 }
827
828 /*
829   XClearArea(dpy,window, old_ballx, old_bally, st->ball.d, st->ball.d, 0);
830   XFillRectangle (dpy, window, gc, st->ball.x, st->ball.y, st->ball.d, st->ball.d);
831   XFillRectangle (dpy, window, gc, xgwa.width / 2, 0, st->ball.d, xgwa.height);
832 */
833
834 static void
835 erase_ball(struct state *st)
836 {
837   analogtv_draw_solid(st->inp,
838                       ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w,
839                       ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h,
840                       st->field_ntsc);
841 }
842
843 static void
844 paint_ball(struct state *st)
845 {
846   analogtv_draw_solid(st->inp,
847                       ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w,
848                       ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h,
849                       st->ball_ntsc);
850 }
851
852 static void
853 paint_score(struct state *st)
854 {
855   char buf[256];
856
857   char* fmt = (st->clock ? "%02d" : "%d");
858
859   analogtv_draw_solid(st->inp,
860                       ANALOGTV_VIS_START, ANALOGTV_VIS_END,
861                       ANALOGTV_TOP, ANALOGTV_TOP + 10+ st->score_font.char_h * st->score_font.y_mult,
862                       st->field_ntsc);
863
864
865   sprintf(buf, fmt ,st->r_paddle.score%256);
866   analogtv_draw_string(st->inp, &st->score_font, buf,
867                        ANALOGTV_VIS_START + 130, ANALOGTV_TOP + 8,
868                        st->score_ntsc);
869
870   sprintf(buf, fmt, st->l_paddle.score%256);
871   analogtv_draw_string(st->inp, &st->score_font, buf,
872                        ANALOGTV_VIS_END - 200, ANALOGTV_TOP + 8,
873                        st->score_ntsc);
874
875 }
876
877 static void
878 paint_net(struct state *st)
879 {
880   int x,y;
881
882   x=(ANALOGTV_VIS_START + ANALOGTV_VIS_END)/2;
883
884   for (y=ANALOGTV_TOP; y<ANALOGTV_BOT; y+=6) {
885     analogtv_draw_solid(st->inp, x-2, x+2, y, y+3,
886                         st->net_ntsc);
887     analogtv_draw_solid(st->inp, x-2, x+2, y+3, y+6,
888                         st->field_ntsc);
889
890   }
891 }
892
893 static double
894 double_time (void)
895 {
896   struct timeval now;
897 # ifdef GETTIMEOFDAY_TWO_ARGS
898   struct timezone tzp;
899   gettimeofday(&now, &tzp);
900 # else
901   gettimeofday(&now);
902 # endif
903
904   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
905 }
906
907 static unsigned long
908 pong_draw (Display *dpy, Window window, void *closure)
909 {
910   struct state *st = (struct state *) closure;
911   const analogtv_reception *reception = &st->reception;
912   double then = double_time(), now, timedelta;
913
914   if (st->clock)
915   {
916     time_t now = time(0);
917     struct tm* tm_now = localtime(&now);
918
919     if (st->r_paddle.score != tm_now->tm_hour)
920     {
921       /* l paddle must score */
922       st->r_paddle.wait = 1;
923     }
924     else if (st->l_paddle.score != tm_now->tm_min)
925     {
926       /* r paddle must score */
927       st->l_paddle.wait = 1;
928     }
929   }
930   erase_ball(st);
931
932   st->ball.x += st->bx;
933   st->ball.y += st->by;
934
935   if (!st->clock)
936   {
937     /* in non-clock mode, occasionally increase ball speed */
938     if ((random()%40)==0) {
939       if (st->bx>0) st->bx++; else st->bx--;
940     }
941   }
942
943   p_logic(st, &st->r_paddle);
944   p_logic(st, &st->l_paddle);
945
946   p_hit_top_bottom(&st->r_paddle);
947   p_hit_top_bottom(&st->l_paddle);
948
949   hit_top_bottom(st);
950   hit_paddle(st);
951
952   #ifdef OUTPUT_POS
953   printf("(%d,%d,%d,%d)\n",st->ball.x,st->ball.y,st->ball.w,st->ball.h);
954   #endif
955
956   paint_score(st);
957
958   paint_net(st);
959
960   if (1) {
961     paint_paddle(st, &st->r_paddle);
962     paint_paddle(st, &st->l_paddle);
963   }
964   if (1) paint_ball(st);
965
966   analogtv_reception_update(&st->reception);
967   analogtv_draw(st->tv, st->noise, &reception, 1);
968
969   now = double_time();
970   timedelta = (1 / 29.97) - (now - then);
971   return timedelta > 0 ? timedelta * 1000000 : 0;
972 }
973
974 \f
975
976 static const char *pong_defaults [] = {
977   ".background: black",
978   ".foreground: white",
979   "*speed:      6",
980   "*noise:      0.04",
981   "*clock:      false",
982   "*p1:         ai",
983   "*p2:         ai",
984   ANALOGTV_DEFAULTS
985   0
986 };
987
988 static XrmOptionDescRec pong_options [] = {
989   { "-speed",           ".speed",     XrmoptionSepArg, 0 },
990   { "-noise",           ".noise",     XrmoptionSepArg, 0 },
991   { "-clock",           ".clock",     XrmoptionNoArg, "true" },
992   { "-p1",              ".p1",        XrmoptionSepArg, 0 },
993   { "-p2",              ".p2",        XrmoptionSepArg, 0 },
994   ANALOGTV_OPTIONS
995   { 0, 0, 0, 0 }
996 };
997
998 static void
999 pong_reshape (Display *dpy, Window window, void *closure, 
1000                  unsigned int w, unsigned int h)
1001 {
1002   struct state *st = (struct state *) closure;
1003   XWindowAttributes xgwa;
1004   analogtv_reconfigure (st->tv);
1005
1006   XGetWindowAttributes(dpy, window, &xgwa); /* AnalogTV does this too. */
1007   xgwa.width = w;
1008   xgwa.height = h;
1009   do_shape(st, &xgwa);
1010 }
1011
1012 static Bool
1013 pong_event (Display *dpy, Window window, void *closure, XEvent *event)
1014 {
1015   struct state *st = (struct state *) closure;
1016   switch (event->type)
1017   {
1018   case MotionNotify:
1019     st->mouse_y = event->xmotion.y;
1020     break;
1021 # ifndef HAVE_JWXYZ
1022   case FocusIn:
1023     if (needs_grab(st))
1024     {
1025       grab_pointer(st);
1026     }
1027     break;
1028   case FocusOut:
1029     if (needs_grab(st))
1030     {
1031       XUngrabPointer (dpy, CurrentTime);
1032       st->is_focused = False;
1033     }
1034     break;
1035 # endif /* !HAVE_JWXYZ */
1036   case KeyPress:
1037   case KeyRelease:
1038     {
1039       char c;
1040       KeySym key;
1041       XLookupString(&event->xkey, &c, 1, &key, 0);
1042       Bool is_pressed = event->type == KeyPress;
1043       switch(key)
1044       {
1045       case XK_Up:
1046         if (st->l_paddle.player == PLAYER_KEYBOARD ||
1047             st->r_paddle.player == PLAYER_KEYBOARD)
1048           {
1049             st->key_up = is_pressed;
1050             return True;
1051           }
1052         break;
1053       case XK_Down:
1054         if (st->l_paddle.player == PLAYER_KEYBOARD ||
1055             st->r_paddle.player == PLAYER_KEYBOARD)
1056           {
1057             st->key_down = is_pressed;
1058             return True;
1059           }
1060         break;
1061       case 'w':
1062         if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT ||
1063             st->r_paddle.player == PLAYER_KEYBOARD_LEFT)
1064           {
1065             st->key_w = is_pressed;
1066             return True;
1067           }
1068         break;
1069       case 's':
1070         if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT ||
1071             st->r_paddle.player == PLAYER_KEYBOARD_LEFT)
1072           {
1073             st->key_s = is_pressed;
1074             return True;
1075           }
1076         break;
1077       }
1078     }
1079
1080   /* Allow the user to pick up and drag either paddle with the mouse,
1081      even when not in a mouse-paddle mode. */
1082
1083   case ButtonPress:
1084     if (st->dragging != 0)
1085       return False;
1086     else if (event->xbutton.x < st->w * 0.2)
1087       {
1088         if (st->l_paddle.player != PLAYER_MOUSE)
1089           st->dragging = 1;
1090         return True;
1091       }
1092     else if (event->xbutton.x > st->w * 0.8)
1093       {
1094         if (st->r_paddle.player != PLAYER_MOUSE)
1095           st->dragging = 2;
1096         return True;
1097       }
1098     break;
1099   case ButtonRelease:
1100     if (st->dragging != 0)
1101       {
1102         st->dragging = 0;
1103         return True;
1104       }
1105     break;
1106   }
1107   return False;
1108 }
1109
1110 static void
1111 pong_free (Display *dpy, Window window, void *closure)
1112 {
1113   struct state *st = (struct state *) closure;
1114   analogtv_release(st->tv);
1115   free (st);
1116 }
1117
1118 XSCREENSAVER_MODULE ("Pong", pong)