025a0c1914089decd7a9e8355255429d9afcc9e0
[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 hit_top_bottom(struct state *st)
110 {
111   if ( (st->ball.y <= PONG_TMARG) ||
112        (st->ball.y+st->ball.h >= PONG_H) )
113     st->by=-st->by;
114 }
115
116 static void
117 reset_score(struct state * st)
118 {
119   if (st->clock)
120   {
121     /* init score to current time */
122     time_t now = time(0);
123     struct tm* now_tm = localtime(&now);
124
125     st->r_paddle.score = now_tm->tm_hour;
126     st->l_paddle.score = now_tm->tm_min;
127   }
128   else
129   {
130     st->r_paddle.score = 0;
131     st->l_paddle.score = 0;
132   }
133 }
134
135 static void
136 new_game(struct state *st)
137 {
138   /* Starts a Whole New Game*/
139   st->ball.x = PONG_W/2;
140   st->ball.y = PONG_H/2;
141   st->bx = st->m_unit;
142   st->by = st->m_unit;
143
144   /* jwz: perhaps not totally accurate, but randomize things a little bit
145      so that games on multiple screens are not identical. */
146   if (random() & 1) st->by = -st->by;
147   st->ball.y += (random() % (PONG_H/6))-(PONG_H/3);
148
149   st->l_paddle.wait = 1;
150   st->l_paddle.lock = 0;
151   st->r_paddle.wait = 0;
152   st->r_paddle.lock = 0;
153   st->paddle_rate = st->m_unit-1;
154   reset_score(st);
155
156   st->l_paddle.h = PONG_H/4;
157   st->r_paddle.h = PONG_H/4;
158 }
159
160 static void
161 start_game(struct state *st)
162 {
163   /*Init the ball*/
164   st->ball.x = PONG_W/2;
165   st->ball.y = PONG_H/2;
166   st->bx = st->m_unit;
167   st->by = st->m_unit;
168
169   /* jwz: perhaps not totally accurate, but randomize things a little bit
170      so that games on multiple screens are not identical. */
171   if (random() & 1) st->by = -st->by;
172   st->ball.y += (random() % (PONG_H/6))-(PONG_H/3);
173
174   st->l_paddle.wait = 1;
175   st->l_paddle.lock = 0;
176   st->r_paddle.wait = 0;
177   st->r_paddle.lock = 0;
178   st->paddle_rate = st->m_unit-1;
179
180   if (st->l_paddle.h > 10) st->l_paddle.h= st->l_paddle.h*19/20;
181   if (st->r_paddle.h > 10) st->r_paddle.h= st->r_paddle.h*19/20;
182 }
183
184 static void
185 hit_paddle(struct state *st)
186 {
187   if ( st->ball.x + st->ball.w >= st->r_paddle.x &&
188        st->bx > 0 ) /*we are traveling to the right*/
189     {
190       if ((st->ball.y + st->ball.h > st->r_paddle.y) &&
191           (st->ball.y < st->r_paddle.y + st->r_paddle.h))
192         {
193           st->bx=-st->bx;
194           st->l_paddle.wait = 0;
195           st->r_paddle.wait = 1;
196           st->r_paddle.lock = 0;
197           st->l_paddle.lock = 0;
198         }
199       else
200         {
201           if (st->clock)
202           {
203             reset_score(st);
204           }
205           else
206           {
207             st->r_paddle.score++;
208             if (st->r_paddle.score >=10)
209               new_game(st);
210             else 
211               start_game(st);
212           }
213         }
214     }
215
216   if (st->ball.x <= st->l_paddle.x + st->l_paddle.w &&
217       st->bx < 0 ) /*we are traveling to the left*/
218     {
219       if ( st->ball.y + st->ball.h > st->l_paddle.y &&
220            st->ball.y < st->l_paddle.y + st->l_paddle.h)
221         {
222           st->bx=-st->bx;
223           st->l_paddle.wait = 1;
224           st->r_paddle.wait = 0;
225           st->r_paddle.lock = 0;
226           st->l_paddle.lock = 0;
227         }
228       else
229         {
230           if (st->clock)
231           {
232             reset_score(st);
233           }
234           else
235           {
236             st->l_paddle.score++;
237             if (st->l_paddle.score >= 10)
238               new_game(st);
239             else
240               start_game(st);
241           }
242         }
243     }
244 }
245
246 static void *
247 pong_init (Display *dpy, Window window)
248 {
249   struct state *st = (struct state *) calloc (1, sizeof(*st));
250
251   int i;
252   struct {
253     int w, h;
254     char *s[10];
255   } fonts[2] = { 
256     { /* regular pong font */
257       /* If you think we haven't learned anything since the early 70s,
258          look at this font for a while */
259       4, 6, 
260         { 
261             "****"
262             "*  *"
263             "*  *"
264             "*  *"
265             "*  *"
266             "****",
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     { /* pong clock font - hand-crafted double size looks better */
333       8, 12, 
334         {
335             "####### "
336             "####### "
337             "##   ## "
338             "##   ## "
339             "##   ## "
340             "##   ## "
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   st->dpy = dpy;
460   st->window = window;
461   st->tv=analogtv_allocate(st->dpy, st->window);
462   analogtv_set_defaults(st->tv, "");
463
464
465   st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
466
467   analogtv_make_font(st->dpy, st->window, &st->score_font, 
468                      fonts[st->clock].w, fonts[st->clock].h, NULL);
469
470   for (i=0; i<10; ++i)
471   {
472     analogtv_font_set_char(&st->score_font, '0'+i, fonts[st->clock].s[i]);
473   }
474
475 #ifdef OUTPUT_POS
476   printf("screen(%d,%d,%d,%d)\n",0,0,PONG_W,PONG_H);
477 #endif
478
479   st->inp=analogtv_input_allocate();
480   analogtv_setup_sync(st->inp, 0, 0);
481
482   st->reception.input = st->inp;
483   st->reception.level = 2.0;
484   st->reception.ofs=0;
485 #if 0
486   if (random()) {
487     st->reception.multipath = frand(1.0);
488   } else {
489 #endif
490     st->reception.multipath=0.0;
491 #if 0
492   }
493 #endif
494
495   /*Init the paddles*/
496   st->l_paddle.x = 8;
497   st->l_paddle.y = 100;
498   st->l_paddle.w = 16;
499   st->l_paddle.h = PONG_H/4;
500   st->l_paddle.wait = 1;
501   st->l_paddle.lock = 0;
502   st->r_paddle = st->l_paddle;
503   st->r_paddle.x = PONG_W - 8 - st->r_paddle.w;
504   st->r_paddle.wait = 0;
505   /*Init the ball*/
506   st->ball.x = PONG_W/2;
507   st->ball.y = PONG_H/2;
508   st->ball.w = 16;
509   st->ball.h = 8;
510
511   st->m_unit = get_integer_resource (st->dpy, "speed", "Integer");
512   st->noise  = get_float_resource(st->dpy, "noise", "Float");
513   st->clock  = get_boolean_resource(st->dpy, "clock", "Boolean");
514
515   if (!st->clock)
516   {
517     st->score_font.y_mult *= 2;
518     st->score_font.x_mult *= 2;
519   }
520
521   reset_score(st);
522
523   start_game(st);
524
525   analogtv_lcp_to_ntsc(ANALOGTV_BLACK_LEVEL, 0.0, 0.0, st->field_ntsc);
526   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->ball_ntsc);
527   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->paddle_ntsc);
528   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->score_ntsc);
529   analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->net_ntsc);
530
531   analogtv_draw_solid(st->inp,
532                       ANALOGTV_VIS_START, ANALOGTV_VIS_END,
533                       ANALOGTV_TOP, ANALOGTV_BOT,
534                       st->field_ntsc);
535
536   return st;
537 }
538
539 static void
540 p_logic(struct state *st, Paddle *p)
541 {
542   int targ;
543   if (st->bx > 0) {
544     targ = st->ball.y + st->by * (st->r_paddle.x-st->ball.x) / st->bx;
545   }
546   else if (st->bx < 0) {
547     targ = st->ball.y - st->by * (st->ball.x - st->l_paddle.x - st->l_paddle.w) / st->bx;
548   }
549   else {
550     targ = st->ball.y;
551   }
552   if (targ > PONG_H) targ=PONG_H;
553   if (targ < 0) targ=0;
554
555   if (targ < p->y && !p->lock)
556   {
557     p->y -= st->paddle_rate;
558   }
559   else if (targ > (p->y + p->h) && !p->lock)
560   {
561     p->y += st->paddle_rate;
562   }
563   else
564   {
565     int move=targ - (p->y + p->h/2);
566     if (move>st->paddle_rate) move=st->paddle_rate;
567     if (move<-st->paddle_rate) move=-st->paddle_rate;
568     p->y += move;
569     p->lock = 1;
570   }
571 }
572
573 static void
574 p_hit_top_bottom(Paddle *p)
575 {
576   if(p->y <= PONG_TMARG)
577   {
578     p->y = PONG_TMARG;
579   }
580   if((p->y + p->h) >= PONG_H)
581   {
582     p->y = PONG_H - p->h;
583   }
584 }
585
586 /*
587   XFillRectangle (dpy, window, gc, p->x, p->y, p->w, p->h);
588   if (old_v > p->y)
589   {
590     XClearArea(dpy,window, p->x, p->y + p->h,
591       p->w, (old_v + p->h) - (p->y + p->h), 0);
592   }
593   else if (old_v < p->y)
594   {
595     XClearArea(dpy,window, p->x, old_v, p->w, p->y - old_v, 0);
596   }
597 */
598 static void
599 paint_paddle(struct state *st, Paddle *p)
600 {
601   analogtv_draw_solid(st->inp,
602                       ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w,
603                       ANALOGTV_TOP, ANALOGTV_BOT,
604                       st->field_ntsc);
605
606   analogtv_draw_solid(st->inp,
607                       ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w,
608                       ANALOGTV_TOP + p->y, ANALOGTV_TOP + p->y + p->h,
609                       st->paddle_ntsc);
610 }
611
612 /*
613   XClearArea(dpy,window, old_ballx, old_bally, st->ball.d, st->ball.d, 0);
614   XFillRectangle (dpy, window, gc, st->ball.x, st->ball.y, st->ball.d, st->ball.d);
615   XFillRectangle (dpy, window, gc, xgwa.width / 2, 0, st->ball.d, xgwa.height);
616 */
617
618 static void
619 erase_ball(struct state *st)
620 {
621   analogtv_draw_solid(st->inp,
622                       ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w,
623                       ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h,
624                       st->field_ntsc);
625 }
626
627 static void
628 paint_ball(struct state *st)
629 {
630   analogtv_draw_solid(st->inp,
631                       ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w,
632                       ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h,
633                       st->ball_ntsc);
634 }
635
636 static void
637 paint_score(struct state *st)
638 {
639   char buf[256];
640
641   char* fmt = (st->clock ? "%02d" : "%d");
642
643   analogtv_draw_solid(st->inp,
644                       ANALOGTV_VIS_START, ANALOGTV_VIS_END,
645                       ANALOGTV_TOP, ANALOGTV_TOP + 10+ st->score_font.char_h * st->score_font.y_mult,
646                       st->field_ntsc);
647
648
649   sprintf(buf, fmt ,st->r_paddle.score%256);
650   analogtv_draw_string(st->inp, &st->score_font, buf,
651                        ANALOGTV_VIS_START + 130, ANALOGTV_TOP + 8,
652                        st->score_ntsc);
653
654   sprintf(buf, fmt, st->l_paddle.score%256);
655   analogtv_draw_string(st->inp, &st->score_font, buf,
656                        ANALOGTV_VIS_END - 200, ANALOGTV_TOP + 8,
657                        st->score_ntsc);
658
659 }
660
661 static void
662 paint_net(struct state *st)
663 {
664   int x,y;
665
666   x=(ANALOGTV_VIS_START + ANALOGTV_VIS_END)/2;
667
668   for (y=ANALOGTV_TOP; y<ANALOGTV_BOT; y+=6) {
669     analogtv_draw_solid(st->inp, x-2, x+2, y, y+3,
670                         st->net_ntsc);
671     analogtv_draw_solid(st->inp, x-2, x+2, y+3, y+6,
672                         st->field_ntsc);
673
674   }
675 }
676
677 static unsigned long
678 pong_draw (Display *dpy, Window window, void *closure)
679 {
680   struct state *st = (struct state *) closure;
681
682   if (st->clock)
683   {
684     time_t now = time(0);
685     struct tm* tm_now = localtime(&now);
686
687     if (st->r_paddle.score != tm_now->tm_hour)
688     {
689       /* l paddle must score */
690       st->r_paddle.wait = 1;
691     }
692     else if (st->l_paddle.score != tm_now->tm_min)
693     {
694       /* r paddle must score */
695       st->l_paddle.wait = 1;
696     }
697   }
698   erase_ball(st);
699
700   st->ball.x += st->bx;
701   st->ball.y += st->by;
702
703   if (!st->clock)
704   {
705     /* in non-clock mode, occasionally increase ball speed */
706     if ((random()%40)==0) {
707       if (st->bx>0) st->bx++; else st->bx--;
708     }
709   }
710
711   if (!st->r_paddle.wait)
712   {
713     p_logic(st, &st->r_paddle);
714   }
715   if (!st->l_paddle.wait)
716   {
717     p_logic(st, &st->l_paddle);
718   }
719
720   p_hit_top_bottom(&st->r_paddle);
721   p_hit_top_bottom(&st->l_paddle);
722
723   hit_top_bottom(st);
724   hit_paddle(st);
725
726   #ifdef OUTPUT_POS
727   printf("(%d,%d,%d,%d)\n",st->ball.x,st->ball.y,st->ball.w,st->ball.h);
728   #endif
729
730   paint_score(st);
731
732   paint_net(st);
733
734   if (1) {
735     paint_paddle(st, &st->r_paddle);
736     paint_paddle(st, &st->l_paddle);
737   }
738   if (1) paint_ball(st);
739
740   analogtv_init_signal(st->tv, st->noise);
741   analogtv_reception_update(&st->reception);
742   analogtv_add_signal(st->tv, &st->reception);
743   analogtv_draw(st->tv);
744
745   return 10000;
746 }
747
748 \f
749
750 static const char *pong_defaults [] = {
751   ".background: black",
752   ".foreground: white",
753   "*speed:      6",
754   "*noise:      0.04",
755   "*clock:      false",
756   ANALOGTV_DEFAULTS
757   0
758 };
759
760 static XrmOptionDescRec pong_options [] = {
761   { "-speed",           ".speed",     XrmoptionSepArg, 0 },
762   { "-noise",           ".noise",     XrmoptionSepArg, 0 },
763   { "-clock",           ".clock",     XrmoptionNoArg, "true" },
764   ANALOGTV_OPTIONS
765   { 0, 0, 0, 0 }
766 };
767
768 static void
769 pong_reshape (Display *dpy, Window window, void *closure, 
770                  unsigned int w, unsigned int h)
771 {
772   struct state *st = (struct state *) closure;
773   analogtv_reconfigure (st->tv);
774 }
775
776 static Bool
777 pong_event (Display *dpy, Window window, void *closure, XEvent *event)
778 {
779   return False;
780 }
781
782 static void
783 pong_free (Display *dpy, Window window, void *closure)
784 {
785   struct state *st = (struct state *) closure;
786   analogtv_release(st->tv);
787   free (st);
788 }
789
790 XSCREENSAVER_MODULE ("Pong", pong)