1 /* pong, Copyright (c) 2003 Jeremy English <jenglish@myself.com>
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.
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
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.
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
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
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:
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?
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
51 * There is some info at http://www.mameworld.net/discrete/Atari/Atari.htm#Pong
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.
58 #include "screenhack.h"
62 # include <X11/keysym.h>
64 /* #define OUTPUT_POS */
74 typedef struct _paddle {
85 typedef struct _ball {
108 analogtv_reception reception;
116 analogtv_font score_font;
122 unsigned w, h, screen_h, screen_h_mm;
128 unsigned int dragging : 2;
133 PONG_W = ANALOGTV_VIS_LEN,
134 PONG_H = ANALOGTV_VISLINES,
139 p_hit_top_bottom(Paddle *p);
142 hit_top_bottom(struct state *st)
144 if ( (st->ball.y <= PONG_TMARG) ||
145 (st->ball.y+st->ball.h >= PONG_H) )
150 reset_score(struct state * st)
154 /* init score to current time */
155 time_t now = time(0);
156 struct tm* now_tm = localtime(&now);
158 st->r_paddle.score = now_tm->tm_hour;
159 st->l_paddle.score = now_tm->tm_min;
163 st->r_paddle.score = 0;
164 st->l_paddle.score = 0;
169 new_game(struct state *st)
171 /* Starts a Whole New Game*/
172 st->ball.x = PONG_W/2;
173 st->ball.y = PONG_H/2;
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);
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;
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);
198 start_game(struct state *st)
201 st->ball.x = PONG_W/2;
202 st->ball.y = PONG_H/2;
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);
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;
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;
222 hit_paddle(struct state *st)
224 if ( st->ball.x + st->ball.w >= st->r_paddle.x &&
225 st->bx > 0 ) /*we are traveling to the right*/
227 if ((st->ball.y + st->ball.h > st->r_paddle.y) &&
228 (st->ball.y < st->r_paddle.y + st->r_paddle.h))
231 st->l_paddle.wait = 0;
232 st->r_paddle.wait = 1;
233 st->r_paddle.lock = 0;
234 st->l_paddle.lock = 0;
244 st->r_paddle.score++;
245 if (st->r_paddle.score >=10)
253 if (st->ball.x <= st->l_paddle.x + st->l_paddle.w &&
254 st->bx < 0 ) /*we are traveling to the left*/
256 if ( st->ball.y + st->ball.h > st->l_paddle.y &&
257 st->ball.y < st->l_paddle.y + st->l_paddle.h)
260 st->l_paddle.wait = 1;
261 st->r_paddle.wait = 0;
262 st->r_paddle.lock = 0;
263 st->l_paddle.lock = 0;
273 st->l_paddle.score++;
274 if (st->l_paddle.score >= 10)
284 get_player_type(Display *dpy, char *rsrc)
287 char *s = get_string_resource(dpy, rsrc, "String");
288 if (!strcmp(s, "ai") || !strcmp(s, "AI"))
293 else if (!strcmp(s, "mouse"))
295 result = PLAYER_MOUSE;
298 else if (!strcmp(s, "tab") || !strcmp(s, "tablet"))
300 result = PLAYER_TABLET;
302 else if (!strcmp(s, "kb") || !strcmp(s, "keyb") || !strcmp(s, "keyboard") ||
303 !strcmp(s, "right") || !strcmp(s, "kbright") ||
304 !strcmp(s, "arrows"))
306 result = PLAYER_KEYBOARD;
308 else if (!strcmp(s, "left") || !strcmp(s, "kbleft") ||
309 !strcmp(s, "ws") || !strcmp(s, "wasd"))
311 result = PLAYER_KEYBOARD_LEFT;
315 fprintf(stderr, "%s: invalid player type\n", progname);
323 do_shape (struct state *st, const XWindowAttributes *xgwa)
326 st->h = xgwa->height;
327 st->screen_h = XHeightOfScreen(xgwa->screen);
328 st->screen_h_mm = XHeightMMOfScreen(xgwa->screen);
333 needs_grab (struct state *st)
336 st->l_paddle.player == PLAYER_MOUSE ||
337 st->r_paddle.player == PLAYER_MOUSE;
339 st->l_paddle.player == PLAYER_TABLET ||
340 st->r_paddle.player == PLAYER_TABLET;
345 grab_pointer (struct state *st)
347 st->is_focused = True;
348 XGrabPointer(st->dpy, st->window, True, PointerMotionMask, GrabModeAsync,
349 GrabModeAsync, st->window, st->null_cursor, CurrentTime);
351 #endif /* !HAVE_JWXYZ */
354 pong_init (Display *dpy, Window window)
356 struct state *st = (struct state *) calloc (1, sizeof(*st));
359 XWindowAttributes xgwa;
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 */
440 { /* pong clock font - hand-crafted double size looks better */
569 st->tv=analogtv_allocate(st->dpy, st->window);
570 analogtv_set_defaults(st->tv, "");
573 st->clock = get_boolean_resource(st->dpy, "clock", "Boolean");
575 analogtv_make_font(st->dpy, st->window, &st->score_font,
576 fonts[st->clock].w, fonts[st->clock].h, NULL);
580 analogtv_font_set_char(&st->score_font, '0'+i, fonts[st->clock].s[i]);
584 printf("screen(%d,%d,%d,%d)\n",0,0,PONG_W,PONG_H);
587 st->inp=analogtv_input_allocate();
588 analogtv_setup_sync(st->inp, 0, 0);
590 st->reception.input = st->inp;
591 st->reception.level = 2.0;
595 st->reception.multipath = frand(1.0);
598 st->reception.multipath=0.0;
604 st->l_paddle.player = get_player_type(dpy, "p1");
606 st->l_paddle.y = 100;
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;
616 st->ball.x = PONG_W/2;
617 st->ball.y = PONG_H/2;
621 /* The mouse warping business breaks tablet input. */
622 if (st->l_paddle.player == PLAYER_MOUSE &&
623 st->r_paddle.player == PLAYER_TABLET)
625 st->l_paddle.player = PLAYER_TABLET;
627 if (st->r_paddle.player == PLAYER_MOUSE &&
628 st->l_paddle.player == PLAYER_TABLET)
630 st->r_paddle.player = PLAYER_TABLET;
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);
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)
646 XColor black = {0, 0, 0, 0};
647 Pixmap cursor_pix = XCreatePixmap(dpy, window, 4, 4, 1);
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);
658 XSelectInput(dpy, window,
659 PointerMotionMask | FocusChangeMask |
660 KeyPressMask | KeyReleaseMask |
661 ButtonPressMask | ButtonReleaseMask);
669 XDefineCursor(dpy, window, st->null_cursor);
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");
680 st->score_font.y_mult *= 2;
681 st->score_font.x_mult *= 2;
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);
694 analogtv_draw_solid(st->inp,
695 ANALOGTV_VIS_START, ANALOGTV_VIS_END,
696 ANALOGTV_TOP, ANALOGTV_BOT,
699 XGetWindowAttributes(dpy, window, &xgwa);
706 p_logic(struct state *st, Paddle *p)
708 if (p->player == PLAYER_AI)
714 targ = st->ball.y + st->by * (st->r_paddle.x-st->ball.x) / st->bx;
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;
722 if (targ > PONG_H) targ=PONG_H;
723 if (targ < 0) targ=0;
725 if (targ < p->y && !p->lock)
727 p->y -= st->paddle_rate;
729 else if (targ > (p->y + p->h) && !p->lock)
731 p->y += st->paddle_rate;
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;
744 else if (p->player == PLAYER_MOUSE)
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);
751 XWarpPointer (st->dpy, None, st->window, 0, 0, 0, 0, st->w / 2, st->h / 2);
754 else if (p->player == PLAYER_TABLET)
756 p->y = st->mouse_y * (PONG_H - PONG_TMARG) / st->h + PONG_TMARG - p->h / 2;
758 else if (p->player == PLAYER_KEYBOARD)
765 else if (p->player == PLAYER_KEYBOARD_LEFT)
773 if ((st->dragging == 1 && p == &st->l_paddle) ||
774 (st->dragging == 2 && p == &st->r_paddle))
776 /* Not getting MotionNotify. */
777 Window root1, child1;
778 int mouse_x, mouse_y, root_x, root_y;
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;
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;
790 p_hit_top_bottom(Paddle *p)
792 if(p->y <= PONG_TMARG)
796 if((p->y + p->h) >= PONG_H)
798 p->y = PONG_H - p->h;
803 XFillRectangle (dpy, window, gc, p->x, p->y, p->w, p->h);
806 XClearArea(dpy,window, p->x, p->y + p->h,
807 p->w, (old_v + p->h) - (p->y + p->h), 0);
809 else if (old_v < p->y)
811 XClearArea(dpy,window, p->x, old_v, p->w, p->y - old_v, 0);
815 paint_paddle(struct state *st, Paddle *p)
817 analogtv_draw_solid(st->inp,
818 ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w,
819 ANALOGTV_TOP, ANALOGTV_BOT,
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,
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);
835 erase_ball(struct state *st)
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,
844 paint_ball(struct state *st)
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,
853 paint_score(struct state *st)
857 char* fmt = (st->clock ? "%02d" : "%d");
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,
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,
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,
878 paint_net(struct state *st)
882 x=(ANALOGTV_VIS_START + ANALOGTV_VIS_END)/2;
884 for (y=ANALOGTV_TOP; y<ANALOGTV_BOT; y+=6) {
885 analogtv_draw_solid(st->inp, x-2, x+2, y, y+3,
887 analogtv_draw_solid(st->inp, x-2, x+2, y+3, y+6,
897 # ifdef GETTIMEOFDAY_TWO_ARGS
899 gettimeofday(&now, &tzp);
904 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
908 pong_draw (Display *dpy, Window window, void *closure)
910 struct state *st = (struct state *) closure;
911 const analogtv_reception *reception = &st->reception;
912 double then = double_time(), now, timedelta;
916 time_t now = time(0);
917 struct tm* tm_now = localtime(&now);
919 if (st->r_paddle.score != tm_now->tm_hour)
921 /* l paddle must score */
922 st->r_paddle.wait = 1;
924 else if (st->l_paddle.score != tm_now->tm_min)
926 /* r paddle must score */
927 st->l_paddle.wait = 1;
932 st->ball.x += st->bx;
933 st->ball.y += st->by;
937 /* in non-clock mode, occasionally increase ball speed */
938 if ((random()%40)==0) {
939 if (st->bx>0) st->bx++; else st->bx--;
943 p_logic(st, &st->r_paddle);
944 p_logic(st, &st->l_paddle);
946 p_hit_top_bottom(&st->r_paddle);
947 p_hit_top_bottom(&st->l_paddle);
953 printf("(%d,%d,%d,%d)\n",st->ball.x,st->ball.y,st->ball.w,st->ball.h);
961 paint_paddle(st, &st->r_paddle);
962 paint_paddle(st, &st->l_paddle);
964 if (1) paint_ball(st);
966 analogtv_reception_update(&st->reception);
967 analogtv_draw(st->tv, st->noise, &reception, 1);
970 timedelta = (1 / 29.97) - (now - then);
971 return timedelta > 0 ? timedelta * 1000000 : 0;
976 static const char *pong_defaults [] = {
977 ".background: black",
978 ".foreground: white",
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 },
999 pong_reshape (Display *dpy, Window window, void *closure,
1000 unsigned int w, unsigned int h)
1002 struct state *st = (struct state *) closure;
1003 XWindowAttributes xgwa;
1004 analogtv_reconfigure (st->tv);
1006 XGetWindowAttributes(dpy, window, &xgwa); /* AnalogTV does this too. */
1009 do_shape(st, &xgwa);
1013 pong_event (Display *dpy, Window window, void *closure, XEvent *event)
1015 struct state *st = (struct state *) closure;
1016 switch (event->type)
1019 st->mouse_y = event->xmotion.y;
1031 XUngrabPointer (dpy, CurrentTime);
1032 st->is_focused = False;
1035 # endif /* !HAVE_JWXYZ */
1041 XLookupString(&event->xkey, &c, 1, &key, 0);
1042 Bool is_pressed = event->type == KeyPress;
1046 if (st->l_paddle.player == PLAYER_KEYBOARD ||
1047 st->r_paddle.player == PLAYER_KEYBOARD)
1049 st->key_up = is_pressed;
1054 if (st->l_paddle.player == PLAYER_KEYBOARD ||
1055 st->r_paddle.player == PLAYER_KEYBOARD)
1057 st->key_down = is_pressed;
1062 if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT ||
1063 st->r_paddle.player == PLAYER_KEYBOARD_LEFT)
1065 st->key_w = is_pressed;
1070 if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT ||
1071 st->r_paddle.player == PLAYER_KEYBOARD_LEFT)
1073 st->key_s = is_pressed;
1080 /* Allow the user to pick up and drag either paddle with the mouse,
1081 even when not in a mouse-paddle mode. */
1084 if (st->dragging != 0)
1086 else if (event->xbutton.x < st->w * 0.2)
1088 if (st->l_paddle.player != PLAYER_MOUSE)
1092 else if (event->xbutton.x > st->w * 0.8)
1094 if (st->r_paddle.player != PLAYER_MOUSE)
1100 if (st->dragging != 0)
1111 pong_free (Display *dpy, Window window, void *closure)
1113 struct state *st = (struct state *) closure;
1114 analogtv_release(st->tv);
1118 XSCREENSAVER_MODULE ("Pong", pong)