From http://www.jwz.org/xscreensaver/xscreensaver-5.33.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  * Permission to use, copy, modify, distribute, and sell this software and its
26  * documentation for any purpose is hereby granted without fee, provided that
27  * the above copyright notice appear in all copies and that both that
28  * copyright notice and this permission notice appear in supporting
29  * documentation.  No representations are made about the suitability of this
30  * software for any purpose.  It is provided "as is" without express or
31  * implied warranty.
32  */
33
34 /*
35  * TLB sez: I haven't actually seen a pong game since I was about 9. Can
36  * someone who has one make this look more realistic? Issues:
37  *
38  *  - the font for scores is wrong. For example '0' was square.
39  *  - was there some kind of screen display when someone won?
40  *  - did the ball move smoothly, or was the X or Y position quantized?
41  *
42  * It could also use better player logic: moving the paddle even when the ball
43  * is going away, and making mistakes instead of just not keeping up with the
44  * speeding ball.
45  *
46  * There is some info at http://www.mameworld.net/discrete/Atari/Atari.htm#Pong
47  *
48  * It says that the original Pong game did not have a microprocessor, or even a
49  * custom integrated circuit. It was all discrete logic.
50  *
51  */
52
53 #include "screenhack.h"
54 #include "analogtv.h"
55 /* #define OUTPUT_POS */
56
57 typedef struct _paddle {
58   int x;
59   int y;
60   int w;
61   int h;
62   int wait;
63   int lock;
64   int score;
65 } Paddle;
66
67 typedef struct _ball {
68   int x;
69   int y;
70   int w;
71   int h;
72 } Ball;
73
74 struct state {
75   Display *dpy;
76   Window window;
77
78   int clock;
79
80   Paddle l_paddle;
81   Paddle r_paddle;
82   Ball ball;
83   int bx,by;
84   int m_unit;
85   int paddle_rate;
86   double noise;
87
88   analogtv *tv;
89   analogtv_input *inp;
90   analogtv_reception reception;
91
92   int paddle_ntsc[4];
93   int field_ntsc[4];
94   int ball_ntsc[4];
95   int score_ntsc[4];
96   int net_ntsc[4];
97
98   analogtv_font score_font;
99 };
100
101
102 enum {
103   PONG_W = ANALOGTV_VIS_LEN,
104   PONG_H = ANALOGTV_VISLINES,
105   PONG_TMARG = 10
106 };
107
108 static void
109 p_hit_top_bottom(Paddle *p);
110
111 static void
112 hit_top_bottom(struct state *st)
113 {
114   if ( (st->ball.y <= PONG_TMARG) ||
115        (st->ball.y+st->ball.h >= PONG_H) )
116     st->by=-st->by;
117 }
118
119 static void
120 reset_score(struct state * st)
121 {
122   if (st->clock)
123   {
124     /* init score to current time */
125     time_t now = time(0);
126     struct tm* now_tm = localtime(&now);
127
128     st->r_paddle.score = now_tm->tm_hour;
129     st->l_paddle.score = now_tm->tm_min;
130   }
131   else
132   {
133     st->r_paddle.score = 0;
134     st->l_paddle.score = 0;
135   }
136 }
137
138 static void
139 new_game(struct state *st)
140 {
141   /* Starts a Whole New Game*/
142   st->ball.x = PONG_W/2;
143   st->ball.y = PONG_H/2;
144   st->bx = st->m_unit;
145   st->by = st->m_unit;
146
147   /* jwz: perhaps not totally accurate, but randomize things a little bit
148      so that games on multiple screens are not identical. */
149   if (random() & 1) st->by = -st->by;
150   st->ball.y += (random() % (PONG_H/6))-(PONG_H/3);
151
152   st->l_paddle.wait = 1;
153   st->l_paddle.lock = 0;
154   st->r_paddle.wait = 0;
155   st->r_paddle.lock = 0;
156   st->paddle_rate = st->m_unit-1;
157   reset_score(st);
158
159   st->l_paddle.h = PONG_H/4;
160   st->r_paddle.h = PONG_H/4;
161   /* Adjust paddle position again, because
162      paddle length is enlarged (reset) above. */
163   p_hit_top_bottom(&st->l_paddle);
164   p_hit_top_bottom(&st->r_paddle);
165 }
166
167 static void
168 start_game(struct state *st)
169 {
170   /*Init the ball*/
171   st->ball.x = PONG_W/2;
172   st->ball.y = PONG_H/2;
173   st->bx = st->m_unit;
174   st->by = st->m_unit;
175
176   /* jwz: perhaps not totally accurate, but randomize things a little bit
177      so that games on multiple screens are not identical. */
178   if (random() & 1) st->by = -st->by;
179   st->ball.y += (random() % (PONG_H/6))-(PONG_H/3);
180
181   st->l_paddle.wait = 1;
182   st->l_paddle.lock = 0;
183   st->r_paddle.wait = 0;
184   st->r_paddle.lock = 0;
185   st->paddle_rate = st->m_unit-1;
186
187   if (st->l_paddle.h > 10) st->l_paddle.h= st->l_paddle.h*19/20;
188   if (st->r_paddle.h > 10) st->r_paddle.h= st->r_paddle.h*19/20;
189 }
190
191 static void
192 hit_paddle(struct state *st)
193 {
194   if ( st->ball.x + st->ball.w >= st->r_paddle.x &&
195        st->bx > 0 ) /*we are traveling to the right*/
196     {
197       if ((st->ball.y + st->ball.h > st->r_paddle.y) &&
198           (st->ball.y < st->r_paddle.y + st->r_paddle.h))
199         {
200           st->bx=-st->bx;
201           st->l_paddle.wait = 0;
202           st->r_paddle.wait = 1;
203           st->r_paddle.lock = 0;
204           st->l_paddle.lock = 0;
205         }
206       else
207         {
208           if (st->clock)
209           {
210             reset_score(st);
211           }
212           else
213           {
214             st->r_paddle.score++;
215             if (st->r_paddle.score >=10)
216               new_game(st);
217             else 
218               start_game(st);
219           }
220         }
221     }
222
223   if (st->ball.x <= st->l_paddle.x + st->l_paddle.w &&
224       st->bx < 0 ) /*we are traveling to the left*/
225     {
226       if ( st->ball.y + st->ball.h > st->l_paddle.y &&
227            st->ball.y < st->l_paddle.y + st->l_paddle.h)
228         {
229           st->bx=-st->bx;
230           st->l_paddle.wait = 1;
231           st->r_paddle.wait = 0;
232           st->r_paddle.lock = 0;
233           st->l_paddle.lock = 0;
234         }
235       else
236         {
237           if (st->clock)
238           {
239             reset_score(st);
240           }
241           else
242           {
243             st->l_paddle.score++;
244             if (st->l_paddle.score >= 10)
245               new_game(st);
246             else
247               start_game(st);
248           }
249         }
250     }
251 }
252
253 static void *
254 pong_init (Display *dpy, Window window)
255 {
256   struct state *st = (struct state *) calloc (1, sizeof(*st));
257
258   int i;
259   struct {
260     int w, h;
261     char *s[10];
262   } fonts[2] = { 
263     { /* regular pong font */
264       /* If you think we haven't learned anything since the early 70s,
265          look at this font for a while */
266       4, 6, 
267         { 
268             "****"
269             "*  *"
270             "*  *"
271             "*  *"
272             "*  *"
273             "****",
274
275             "   *"
276             "   *"
277             "   *"
278             "   *"
279             "   *"
280             "   *",
281
282             "****"
283             "   *"
284             "****"
285             "*   "
286             "*   "
287             "****",
288
289             "****"
290             "   *" 
291             "****"
292             "   *"
293             "   *"
294             "****",
295
296             "*  *"
297             "*  *"
298             "****"
299             "   *"
300             "   *"
301             "   *",
302
303             "****"
304             "*   "
305             "****"
306             "   *"
307             "   *"
308             "****",
309
310             "****"
311             "*   "
312             "****"
313             "*  *"
314             "*  *"
315             "****",
316
317             "****"
318             "   *"
319             "   *"
320             "   *"
321             "   *"
322             "   *",
323
324             "****"
325             "*  *"
326             "****"
327             "*  *"
328             "*  *"
329             "****",
330
331             "****"
332             "*  *"
333             "****"
334             "   *"
335             "   *"
336             "   *"
337         } 
338     },
339     { /* pong clock font - hand-crafted double size looks better */
340       8, 12, 
341         {
342             "####### "
343             "####### "
344             "##   ## "
345             "##   ## "
346             "##   ## "
347             "##   ## "
348             "##   ## "
349             "##   ## "
350             "##   ## "
351             "####### "
352             "####### ",
353             
354             "   ##   "
355             "   ##   "
356             "   ##   "
357             "   ##   "
358             "   ##   "
359             "   ##   "
360             "   ##   "
361             "   ##   "
362             "   ##   "
363             "   ##   "
364             "   ##   ",
365
366             "####### "
367             "####### "
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             "##   ## "
441             "##   ## "
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   st->dpy = dpy;
467   st->window = window;
468   st->tv=analogtv_allocate(st->dpy, st->window);
469   analogtv_set_defaults(st->tv, "");
470
471
472   st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
473
474   analogtv_make_font(st->dpy, st->window, &st->score_font, 
475                      fonts[st->clock].w, fonts[st->clock].h, NULL);
476
477   for (i=0; i<10; ++i)
478   {
479     analogtv_font_set_char(&st->score_font, '0'+i, fonts[st->clock].s[i]);
480   }
481
482 #ifdef OUTPUT_POS
483   printf("screen(%d,%d,%d,%d)\n",0,0,PONG_W,PONG_H);
484 #endif
485
486   st->inp=analogtv_input_allocate();
487   analogtv_setup_sync(st->inp, 0, 0);
488
489   st->reception.input = st->inp;
490   st->reception.level = 2.0;
491   st->reception.ofs=0;
492 #if 0
493   if (random()) {
494     st->reception.multipath = frand(1.0);
495   } else {
496 #endif
497     st->reception.multipath=0.0;
498 #if 0
499   }
500 #endif
501
502   /*Init the paddles*/
503   st->l_paddle.x = 8;
504   st->l_paddle.y = 100;
505   st->l_paddle.w = 16;
506   st->l_paddle.h = PONG_H/4;
507   st->l_paddle.wait = 1;
508   st->l_paddle.lock = 0;
509   st->r_paddle = st->l_paddle;
510   st->r_paddle.x = PONG_W - 8 - st->r_paddle.w;
511   st->r_paddle.wait = 0;
512   /*Init the ball*/
513   st->ball.x = PONG_W/2;
514   st->ball.y = PONG_H/2;
515   st->ball.w = 16;
516   st->ball.h = 8;
517
518   st->m_unit = get_integer_resource (st->dpy, "speed", "Integer");
519   st->noise  = get_float_resource(st->dpy, "noise", "Float");
520   st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
521
522   if (!st->clock)
523   {
524     st->score_font.y_mult *= 2;
525     st->score_font.x_mult *= 2;
526   }
527
528   reset_score(st);
529
530   start_game(st);
531
532   analogtv_lcp_to_ntsc(ANALOGTV_BLACK_LEVEL, 0.0, 0.0, st->field_ntsc);
533   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->ball_ntsc);
534   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->paddle_ntsc);
535   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->score_ntsc);
536   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->net_ntsc);
537
538   analogtv_draw_solid(st->inp,
539                       ANALOGTV_VIS_START, ANALOGTV_VIS_END,
540                       ANALOGTV_TOP, ANALOGTV_BOT,
541                       st->field_ntsc);
542
543   return st;
544 }
545
546 static void
547 p_logic(struct state *st, Paddle *p)
548 {
549   int targ;
550   if (st->bx > 0) {
551     targ = st->ball.y + st->by * (st->r_paddle.x-st->ball.x) / st->bx;
552   }
553   else if (st->bx < 0) {
554     targ = st->ball.y - st->by * (st->ball.x - st->l_paddle.x - st->l_paddle.w) / st->bx;
555   }
556   else {
557     targ = st->ball.y;
558   }
559   if (targ > PONG_H) targ=PONG_H;
560   if (targ < 0) targ=0;
561
562   if (targ < p->y && !p->lock)
563   {
564     p->y -= st->paddle_rate;
565   }
566   else if (targ > (p->y + p->h) && !p->lock)
567   {
568     p->y += st->paddle_rate;
569   }
570   else
571   {
572     int move=targ - (p->y + p->h/2);
573     if (move>st->paddle_rate) move=st->paddle_rate;
574     if (move<-st->paddle_rate) move=-st->paddle_rate;
575     p->y += move;
576     p->lock = 1;
577   }
578 }
579
580 static void
581 p_hit_top_bottom(Paddle *p)
582 {
583   if(p->y <= PONG_TMARG)
584   {
585     p->y = PONG_TMARG;
586   }
587   if((p->y + p->h) >= PONG_H)
588   {
589     p->y = PONG_H - p->h;
590   }
591 }
592
593 /*
594   XFillRectangle (dpy, window, gc, p->x, p->y, p->w, p->h);
595   if (old_v > p->y)
596   {
597     XClearArea(dpy,window, p->x, p->y + p->h,
598       p->w, (old_v + p->h) - (p->y + p->h), 0);
599   }
600   else if (old_v < p->y)
601   {
602     XClearArea(dpy,window, p->x, old_v, p->w, p->y - old_v, 0);
603   }
604 */
605 static void
606 paint_paddle(struct state *st, Paddle *p)
607 {
608   analogtv_draw_solid(st->inp,
609                       ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w,
610                       ANALOGTV_TOP, ANALOGTV_BOT,
611                       st->field_ntsc);
612
613   analogtv_draw_solid(st->inp,
614                       ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w,
615                       ANALOGTV_TOP + p->y, ANALOGTV_TOP + p->y + p->h,
616                       st->paddle_ntsc);
617 }
618
619 /*
620   XClearArea(dpy,window, old_ballx, old_bally, st->ball.d, st->ball.d, 0);
621   XFillRectangle (dpy, window, gc, st->ball.x, st->ball.y, st->ball.d, st->ball.d);
622   XFillRectangle (dpy, window, gc, xgwa.width / 2, 0, st->ball.d, xgwa.height);
623 */
624
625 static void
626 erase_ball(struct state *st)
627 {
628   analogtv_draw_solid(st->inp,
629                       ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w,
630                       ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h,
631                       st->field_ntsc);
632 }
633
634 static void
635 paint_ball(struct state *st)
636 {
637   analogtv_draw_solid(st->inp,
638                       ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w,
639                       ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h,
640                       st->ball_ntsc);
641 }
642
643 static void
644 paint_score(struct state *st)
645 {
646   char buf[256];
647
648   char* fmt = (st->clock ? "%02d" : "%d");
649
650   analogtv_draw_solid(st->inp,
651                       ANALOGTV_VIS_START, ANALOGTV_VIS_END,
652                       ANALOGTV_TOP, ANALOGTV_TOP + 10+ st->score_font.char_h * st->score_font.y_mult,
653                       st->field_ntsc);
654
655
656   sprintf(buf, fmt ,st->r_paddle.score%256);
657   analogtv_draw_string(st->inp, &st->score_font, buf,
658                        ANALOGTV_VIS_START + 130, ANALOGTV_TOP + 8,
659                        st->score_ntsc);
660
661   sprintf(buf, fmt, st->l_paddle.score%256);
662   analogtv_draw_string(st->inp, &st->score_font, buf,
663                        ANALOGTV_VIS_END - 200, ANALOGTV_TOP + 8,
664                        st->score_ntsc);
665
666 }
667
668 static void
669 paint_net(struct state *st)
670 {
671   int x,y;
672
673   x=(ANALOGTV_VIS_START + ANALOGTV_VIS_END)/2;
674
675   for (y=ANALOGTV_TOP; y<ANALOGTV_BOT; y+=6) {
676     analogtv_draw_solid(st->inp, x-2, x+2, y, y+3,
677                         st->net_ntsc);
678     analogtv_draw_solid(st->inp, x-2, x+2, y+3, y+6,
679                         st->field_ntsc);
680
681   }
682 }
683
684 static unsigned long
685 pong_draw (Display *dpy, Window window, void *closure)
686 {
687   struct state *st = (struct state *) closure;
688   const analogtv_reception *reception = &st->reception;
689
690   if (st->clock)
691   {
692     time_t now = time(0);
693     struct tm* tm_now = localtime(&now);
694
695     if (st->r_paddle.score != tm_now->tm_hour)
696     {
697       /* l paddle must score */
698       st->r_paddle.wait = 1;
699     }
700     else if (st->l_paddle.score != tm_now->tm_min)
701     {
702       /* r paddle must score */
703       st->l_paddle.wait = 1;
704     }
705   }
706   erase_ball(st);
707
708   st->ball.x += st->bx;
709   st->ball.y += st->by;
710
711   if (!st->clock)
712   {
713     /* in non-clock mode, occasionally increase ball speed */
714     if ((random()%40)==0) {
715       if (st->bx>0) st->bx++; else st->bx--;
716     }
717   }
718
719   if (!st->r_paddle.wait)
720   {
721     p_logic(st, &st->r_paddle);
722   }
723   if (!st->l_paddle.wait)
724   {
725     p_logic(st, &st->l_paddle);
726   }
727
728   p_hit_top_bottom(&st->r_paddle);
729   p_hit_top_bottom(&st->l_paddle);
730
731   hit_top_bottom(st);
732   hit_paddle(st);
733
734   #ifdef OUTPUT_POS
735   printf("(%d,%d,%d,%d)\n",st->ball.x,st->ball.y,st->ball.w,st->ball.h);
736   #endif
737
738   paint_score(st);
739
740   paint_net(st);
741
742   if (1) {
743     paint_paddle(st, &st->r_paddle);
744     paint_paddle(st, &st->l_paddle);
745   }
746   if (1) paint_ball(st);
747
748   analogtv_reception_update(&st->reception);
749   analogtv_draw(st->tv, st->noise, &reception, 1);
750
751 #ifdef USE_IPHONE
752   return 0;
753 #else
754   return 5000;
755 #endif
756 }
757
758 \f
759
760 static const char *pong_defaults [] = {
761   ".background: black",
762   ".foreground: white",
763   "*speed:      6",
764   "*noise:      0.04",
765   "*clock:      false",
766   ANALOGTV_DEFAULTS
767   0
768 };
769
770 static XrmOptionDescRec pong_options [] = {
771   { "-speed",           ".speed",     XrmoptionSepArg, 0 },
772   { "-noise",           ".noise",     XrmoptionSepArg, 0 },
773   { "-clock",           ".clock",     XrmoptionNoArg, "true" },
774   ANALOGTV_OPTIONS
775   { 0, 0, 0, 0 }
776 };
777
778 static void
779 pong_reshape (Display *dpy, Window window, void *closure, 
780                  unsigned int w, unsigned int h)
781 {
782   struct state *st = (struct state *) closure;
783   analogtv_reconfigure (st->tv);
784 }
785
786 static Bool
787 pong_event (Display *dpy, Window window, void *closure, XEvent *event)
788 {
789   return False;
790 }
791
792 static void
793 pong_free (Display *dpy, Window window, void *closure)
794 {
795   struct state *st = (struct state *) closure;
796   analogtv_release(st->tv);
797   free (st);
798 }
799
800 XSCREENSAVER_MODULE ("Pong", pong)