/* pong, Copyright (c) 2003 Jeremy English * A pong screen saver * * Modified by Brian Sawicki to fix a small bug. * Before this fix after a certain point the paddles would be too * small for the program to effectively hit the ball. The score would * then skyrocket as the paddles missed most every time. Added a max * so that once a paddle gets 10 the entire game restarts. Special * thanks to Scott Zager for some help on this. * * Modified by Trevor Blackwell to use analogtv.[ch] display. * Also added gradual acceleration of the ball, shrinking of paddles, and * scorekeeping. * * Modified by Gereon Steffens to add -clock and -noise * options. See http://www.burovormkrijgers.nl (ugly flash site, * navigate to Portfolio/Browse/Misc/Pong Clock) for the hardware implementation * that gave me the idea. In clock mode, the score reflects the current time, and * the paddles simply stop moving when it's time for the other side to score. This * means that the display is only updated a few seconds *after* the minute actually * changes, but I think this fuzzyness fits well with the display, and since we're * not displaying seconds, who cares. While I was at it, I added a -noise option * to control the noisyness of the display. * * Modified by Dave Odell to add -p1 and -p2 options. * JWXYZ doesn't support XWarpPointer, so PLAYER_MOUSE only works on native * X11. JWXYZ also doesn't support cursors, so PLAYER_TABLET doesn't hide the * mouse pointer. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ /* * TLB sez: I haven't actually seen a pong game since I was about 9. Can * someone who has one make this look more realistic? Issues: * * - the font for scores is wrong. For example '0' was square. * - was there some kind of screen display when someone won? * - did the ball move smoothly, or was the X or Y position quantized? * * It could also use better player logic: moving the paddle even when the ball * is going away, and making mistakes instead of just not keeping up with the * speeding ball. * * There is some info at http://www.mameworld.net/discrete/Atari/Atari.htm#Pong * * It says that the original Pong game did not have a microprocessor, or even a * custom integrated circuit. It was all discrete logic. * */ #include "screenhack.h" #include "analogtv.h" #include #ifndef HAVE_JWXYZ # include #endif /* #define OUTPUT_POS */ typedef enum { PLAYER_AI, PLAYER_MOUSE, PLAYER_TABLET, PLAYER_KEYBOARD, PLAYER_KEYBOARD_LEFT } PlayerType; typedef struct _paddle { PlayerType player; int x; int y; int w; int h; int wait; int lock; int score; } Paddle; typedef struct _ball { int x; int y; int w; int h; } Ball; struct state { Display *dpy; Window window; int clock; Paddle l_paddle; Paddle r_paddle; Ball ball; int bx,by; int m_unit; int paddle_rate; double noise; analogtv *tv; analogtv_input *inp; analogtv_reception reception; int paddle_ntsc[4]; int field_ntsc[4]; int ball_ntsc[4]; int score_ntsc[4]; int net_ntsc[4]; analogtv_font score_font; # ifndef HAVE_JWXYZ Cursor null_cursor; # endif int mouse_y; unsigned w, h, screen_h, screen_h_mm; Bool is_focused; Bool key_w: 1; Bool key_s: 1; Bool key_up: 1; Bool key_down: 1; unsigned int dragging : 2; }; enum { PONG_W = ANALOGTV_VIS_LEN, PONG_H = ANALOGTV_VISLINES, PONG_TMARG = 10 }; static void p_hit_top_bottom(Paddle *p); static void hit_top_bottom(struct state *st) { if ( (st->ball.y <= PONG_TMARG) || (st->ball.y+st->ball.h >= PONG_H) ) st->by=-st->by; } static void reset_score(struct state * st) { if (st->clock) { /* init score to current time */ time_t now = time(0); struct tm* now_tm = localtime(&now); st->r_paddle.score = now_tm->tm_hour; st->l_paddle.score = now_tm->tm_min; } else { st->r_paddle.score = 0; st->l_paddle.score = 0; } } static void new_game(struct state *st) { /* Starts a Whole New Game*/ st->ball.x = PONG_W/2; st->ball.y = PONG_H/2; st->bx = st->m_unit; st->by = st->m_unit; /* jwz: perhaps not totally accurate, but randomize things a little bit so that games on multiple screens are not identical. */ if (random() & 1) st->by = -st->by; st->ball.y += (random() % (PONG_H/6))-(PONG_H/3); st->l_paddle.wait = 1; st->l_paddle.lock = 0; st->r_paddle.wait = 0; st->r_paddle.lock = 0; st->paddle_rate = st->m_unit-1; reset_score(st); st->l_paddle.h = PONG_H/4; st->r_paddle.h = PONG_H/4; /* Adjust paddle position again, because paddle length is enlarged (reset) above. */ p_hit_top_bottom(&st->l_paddle); p_hit_top_bottom(&st->r_paddle); } static void start_game(struct state *st) { /*Init the ball*/ st->ball.x = PONG_W/2; st->ball.y = PONG_H/2; st->bx = st->m_unit; st->by = st->m_unit; /* jwz: perhaps not totally accurate, but randomize things a little bit so that games on multiple screens are not identical. */ if (random() & 1) st->by = -st->by; st->ball.y += (random() % (PONG_H/6))-(PONG_H/3); st->l_paddle.wait = 1; st->l_paddle.lock = 0; st->r_paddle.wait = 0; st->r_paddle.lock = 0; st->paddle_rate = st->m_unit-1; if (st->l_paddle.h > 10) st->l_paddle.h= st->l_paddle.h*19/20; if (st->r_paddle.h > 10) st->r_paddle.h= st->r_paddle.h*19/20; } static void hit_paddle(struct state *st) { if ( st->ball.x + st->ball.w >= st->r_paddle.x && st->bx > 0 ) /*we are traveling to the right*/ { if ((st->ball.y + st->ball.h > st->r_paddle.y) && (st->ball.y < st->r_paddle.y + st->r_paddle.h)) { st->bx=-st->bx; st->l_paddle.wait = 0; st->r_paddle.wait = 1; st->r_paddle.lock = 0; st->l_paddle.lock = 0; } else { if (st->clock) { reset_score(st); } else { st->r_paddle.score++; if (st->r_paddle.score >=10) new_game(st); else start_game(st); } } } if (st->ball.x <= st->l_paddle.x + st->l_paddle.w && st->bx < 0 ) /*we are traveling to the left*/ { if ( st->ball.y + st->ball.h > st->l_paddle.y && st->ball.y < st->l_paddle.y + st->l_paddle.h) { st->bx=-st->bx; st->l_paddle.wait = 1; st->r_paddle.wait = 0; st->r_paddle.lock = 0; st->l_paddle.lock = 0; } else { if (st->clock) { reset_score(st); } else { st->l_paddle.score++; if (st->l_paddle.score >= 10) new_game(st); else start_game(st); } } } } static PlayerType get_player_type(Display *dpy, char *rsrc) { PlayerType result; char *s = get_string_resource(dpy, rsrc, "String"); if (!strcmp(s, "ai") || !strcmp(s, "AI")) { result = PLAYER_AI; } # ifndef HAVE_JWXYZ else if (!strcmp(s, "mouse")) { result = PLAYER_MOUSE; } # endif else if (!strcmp(s, "tab") || !strcmp(s, "tablet")) { result = PLAYER_TABLET; } else if (!strcmp(s, "kb") || !strcmp(s, "keyb") || !strcmp(s, "keyboard") || !strcmp(s, "right") || !strcmp(s, "kbright") || !strcmp(s, "arrows")) { result = PLAYER_KEYBOARD; } else if (!strcmp(s, "left") || !strcmp(s, "kbleft") || !strcmp(s, "ws") || !strcmp(s, "wasd")) { result = PLAYER_KEYBOARD_LEFT; } else { fprintf(stderr, "%s: invalid player type\n", progname); result = PLAYER_AI; } free(s); return result; } static void do_shape (struct state *st, const XWindowAttributes *xgwa) { st->w = xgwa->width; st->h = xgwa->height; st->screen_h = XHeightOfScreen(xgwa->screen); st->screen_h_mm = XHeightMMOfScreen(xgwa->screen); } #ifndef HAVE_JWXYZ static Bool needs_grab (struct state *st) { return st->l_paddle.player == PLAYER_MOUSE || st->r_paddle.player == PLAYER_MOUSE; /* st->l_paddle.player == PLAYER_TABLET || st->r_paddle.player == PLAYER_TABLET; */ } static void grab_pointer (struct state *st) { st->is_focused = True; XGrabPointer(st->dpy, st->window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, st->window, st->null_cursor, CurrentTime); } #endif /* !HAVE_JWXYZ */ static void * pong_init (Display *dpy, Window window) { struct state *st = (struct state *) calloc (1, sizeof(*st)); int i; XWindowAttributes xgwa; struct { int w, h; char *s[10]; } fonts[2] = { { /* regular pong font */ /* If you think we haven't learned anything since the early 70s, look at this font for a while */ 4, 6, { "****" "* *" "* *" "* *" "* *" "****", " *" " *" " *" " *" " *" " *", "****" " *" "****" "* " "* " "****", "****" " *" "****" " *" " *" "****", "* *" "* *" "****" " *" " *" " *", "****" "* " "****" " *" " *" "****", "****" "* " "****" "* *" "* *" "****", "****" " *" " *" " *" " *" " *", "****" "* *" "****" "* *" "* *" "****", "****" "* *" "****" " *" " *" " *" } }, { /* pong clock font - hand-crafted double size looks better */ 8, 12, { "####### " "####### " "## ## " "## ## " "## ## " "## ## " "## ## " "## ## " "## ## " "####### " "####### ", " ## " " ## " " ## " " ## " " ## " " ## " " ## " " ## " " ## " " ## " " ## ", "####### " "####### " " ## " " ## " "####### " "####### " "## " "## " "## " "####### " "####### ", "####### " "####### " " ## " " ## " "####### " "####### " " ## " " ## " " ## " "####### " "####### ", "## ## " "## ## " "## ## " "## ## " "####### " "####### " " ## " " ## " " ## " " ## " " ## ", "####### " "####### " "## " "## " "####### " "####### " " ## " " ## " " ## " "####### " "####### ", "####### " "####### " "## " "## " "####### " "####### " "## ## " "## ## " "## ## " "####### " "####### ", "####### " "####### " " ## " " ## " " ## " " ## " " ## " " ## " " ## " " ## " " ## ", "####### " "####### " "## ## " "## ## " "####### " "####### " "## ## " "## ## " "## ## " "####### " "####### ", "####### " "####### " "## ## " "## ## " "####### " "####### " " ## " " ## " " ## " "####### " "####### " } } }; st->dpy = dpy; st->window = window; st->tv=analogtv_allocate(st->dpy, st->window); analogtv_set_defaults(st->tv, ""); st->clock = get_boolean_resource(st->dpy, "clock", "Boolean"); analogtv_make_font(st->dpy, st->window, &st->score_font, fonts[st->clock].w, fonts[st->clock].h, NULL); for (i=0; i<10; ++i) { analogtv_font_set_char(&st->score_font, '0'+i, fonts[st->clock].s[i]); } #ifdef OUTPUT_POS printf("screen(%d,%d,%d,%d)\n",0,0,PONG_W,PONG_H); #endif st->inp=analogtv_input_allocate(); analogtv_setup_sync(st->inp, 0, 0); st->reception.input = st->inp; st->reception.level = 2.0; st->reception.ofs=0; #if 0 if (random()) { st->reception.multipath = frand(1.0); } else { #endif st->reception.multipath=0.0; #if 0 } #endif /*Init the paddles*/ st->l_paddle.player = get_player_type(dpy, "p1"); st->l_paddle.x = 8; st->l_paddle.y = 100; st->l_paddle.w = 16; st->l_paddle.h = PONG_H/4; st->l_paddle.wait = 1; st->l_paddle.lock = 0; st->r_paddle = st->l_paddle; st->r_paddle.player = get_player_type(dpy, "p2"); st->r_paddle.x = PONG_W - 8 - st->r_paddle.w; st->r_paddle.wait = 0; /*Init the ball*/ st->ball.x = PONG_W/2; st->ball.y = PONG_H/2; st->ball.w = 16; st->ball.h = 8; /* The mouse warping business breaks tablet input. */ if (st->l_paddle.player == PLAYER_MOUSE && st->r_paddle.player == PLAYER_TABLET) { st->l_paddle.player = PLAYER_TABLET; } if (st->r_paddle.player == PLAYER_MOUSE && st->l_paddle.player == PLAYER_TABLET) { st->r_paddle.player = PLAYER_TABLET; } if (st->clock) { st->l_paddle.player = PLAYER_AI; st->r_paddle.player = PLAYER_AI; fprintf(stderr, "%s: clock mode requires AI control\n", progname); } # ifndef HAVE_JWXYZ if (st->l_paddle.player == PLAYER_MOUSE || st->r_paddle.player == PLAYER_MOUSE || st->l_paddle.player == PLAYER_TABLET || st->r_paddle.player == PLAYER_TABLET) { XColor black = {0, 0, 0, 0}; Pixmap cursor_pix = XCreatePixmap(dpy, window, 4, 4, 1); XGCValues gcv; GC mono_gc; gcv.foreground = 0; mono_gc = XCreateGC(dpy, cursor_pix, GCForeground, &gcv); st->null_cursor = XCreatePixmapCursor(dpy, cursor_pix, cursor_pix, &black, &black, 0, 0); XFillRectangle(dpy, cursor_pix, mono_gc, 0, 0, 4, 4); XFreeGC(dpy, mono_gc); XSelectInput(dpy, window, PointerMotionMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask); if (needs_grab(st)) { grab_pointer(st); } else { XDefineCursor(dpy, window, st->null_cursor); } } # endif st->m_unit = get_integer_resource (st->dpy, "speed", "Integer"); st->noise = get_float_resource(st->dpy, "noise", "Float"); st->clock = get_boolean_resource(st->dpy, "clock", "Boolean"); if (!st->clock) { st->score_font.y_mult *= 2; st->score_font.x_mult *= 2; } reset_score(st); start_game(st); analogtv_lcp_to_ntsc(ANALOGTV_BLACK_LEVEL, 0.0, 0.0, st->field_ntsc); analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->ball_ntsc); analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->paddle_ntsc); analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->score_ntsc); analogtv_lcp_to_ntsc(100.0, 0.0, 0.0, st->net_ntsc); analogtv_draw_solid(st->inp, ANALOGTV_VIS_START, ANALOGTV_VIS_END, ANALOGTV_TOP, ANALOGTV_BOT, st->field_ntsc); XGetWindowAttributes(dpy, window, &xgwa); do_shape(st, &xgwa); return st; } static void p_logic(struct state *st, Paddle *p) { if (p->player == PLAYER_AI) { if (!p->wait) { int targ; if (st->bx > 0) { targ = st->ball.y + st->by * (st->r_paddle.x-st->ball.x) / st->bx; } else if (st->bx < 0) { targ = st->ball.y - st->by * (st->ball.x - st->l_paddle.x - st->l_paddle.w) / st->bx; } else { targ = st->ball.y; } if (targ > PONG_H) targ=PONG_H; if (targ < 0) targ=0; if (targ < p->y && !p->lock) { p->y -= st->paddle_rate; } else if (targ > (p->y + p->h) && !p->lock) { p->y += st->paddle_rate; } else { int move=targ - (p->y + p->h/2); if (move>st->paddle_rate) move=st->paddle_rate; if (move<-st->paddle_rate) move=-st->paddle_rate; p->y += move; p->lock = 1; } } } # ifndef HAVE_JWXYZ else if (p->player == PLAYER_MOUSE) { /* Clipping happens elsewhere. */ /* As the screen resolution increases, the mouse moves faster in terms of pixels, so divide by DPI. */ p->y += (int)(st->mouse_y - (st->h / 2)) * 4 * (int)st->screen_h_mm / (3 * (int)st->screen_h); if (st->is_focused) XWarpPointer (st->dpy, None, st->window, 0, 0, 0, 0, st->w / 2, st->h / 2); } # endif else if (p->player == PLAYER_TABLET) { p->y = st->mouse_y * (PONG_H - PONG_TMARG) / st->h + PONG_TMARG - p->h / 2; } else if (p->player == PLAYER_KEYBOARD) { if (st->key_up) p->y -= 8; if (st->key_down) p->y += 8; } else if (p->player == PLAYER_KEYBOARD_LEFT) { if (st->key_w) p->y -= 8; if (st->key_s) p->y += 8; } if ((st->dragging == 1 && p == &st->l_paddle) || (st->dragging == 2 && p == &st->r_paddle)) { /* Not getting MotionNotify. */ Window root1, child1; int mouse_x, mouse_y, root_x, root_y; unsigned int mask; if (XQueryPointer (st->dpy, st->window, &root1, &child1, &root_x, &root_y, &mouse_x, &mouse_y, &mask)) st->mouse_y = mouse_y; if (st->mouse_y < 0) st->mouse_y = 0; p->y = st->mouse_y * (PONG_H - PONG_TMARG) / st->h + PONG_TMARG - p->h / 2; } } static void p_hit_top_bottom(Paddle *p) { if(p->y <= PONG_TMARG) { p->y = PONG_TMARG; } if((p->y + p->h) >= PONG_H) { p->y = PONG_H - p->h; } } /* XFillRectangle (dpy, window, gc, p->x, p->y, p->w, p->h); if (old_v > p->y) { XClearArea(dpy,window, p->x, p->y + p->h, p->w, (old_v + p->h) - (p->y + p->h), 0); } else if (old_v < p->y) { XClearArea(dpy,window, p->x, old_v, p->w, p->y - old_v, 0); } */ static void paint_paddle(struct state *st, Paddle *p) { analogtv_draw_solid(st->inp, ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w, ANALOGTV_TOP, ANALOGTV_BOT, st->field_ntsc); analogtv_draw_solid(st->inp, ANALOGTV_VIS_START + p->x, ANALOGTV_VIS_START + p->x + p->w, ANALOGTV_TOP + p->y, ANALOGTV_TOP + p->y + p->h, st->paddle_ntsc); } /* XClearArea(dpy,window, old_ballx, old_bally, st->ball.d, st->ball.d, 0); XFillRectangle (dpy, window, gc, st->ball.x, st->ball.y, st->ball.d, st->ball.d); XFillRectangle (dpy, window, gc, xgwa.width / 2, 0, st->ball.d, xgwa.height); */ static void erase_ball(struct state *st) { analogtv_draw_solid(st->inp, ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w, ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h, st->field_ntsc); } static void paint_ball(struct state *st) { analogtv_draw_solid(st->inp, ANALOGTV_VIS_START + st->ball.x, ANALOGTV_VIS_START + st->ball.x + st->ball.w, ANALOGTV_TOP + st->ball.y, ANALOGTV_TOP + st->ball.y + st->ball.h, st->ball_ntsc); } static void paint_score(struct state *st) { char buf[256]; char* fmt = (st->clock ? "%02d" : "%d"); analogtv_draw_solid(st->inp, ANALOGTV_VIS_START, ANALOGTV_VIS_END, ANALOGTV_TOP, ANALOGTV_TOP + 10+ st->score_font.char_h * st->score_font.y_mult, st->field_ntsc); sprintf(buf, fmt ,st->r_paddle.score%256); analogtv_draw_string(st->inp, &st->score_font, buf, ANALOGTV_VIS_START + 130, ANALOGTV_TOP + 8, st->score_ntsc); sprintf(buf, fmt, st->l_paddle.score%256); analogtv_draw_string(st->inp, &st->score_font, buf, ANALOGTV_VIS_END - 200, ANALOGTV_TOP + 8, st->score_ntsc); } static void paint_net(struct state *st) { int x,y; x=(ANALOGTV_VIS_START + ANALOGTV_VIS_END)/2; for (y=ANALOGTV_TOP; yinp, x-2, x+2, y, y+3, st->net_ntsc); analogtv_draw_solid(st->inp, x-2, x+2, y+3, y+6, st->field_ntsc); } } static double double_time (void) { struct timeval now; # ifdef GETTIMEOFDAY_TWO_ARGS struct timezone tzp; gettimeofday(&now, &tzp); # else gettimeofday(&now); # endif return (now.tv_sec + ((double) now.tv_usec * 0.000001)); } static unsigned long pong_draw (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; const analogtv_reception *reception = &st->reception; double then = double_time(), now, timedelta; if (st->clock) { time_t now = time(0); struct tm* tm_now = localtime(&now); if (st->r_paddle.score != tm_now->tm_hour) { /* l paddle must score */ st->r_paddle.wait = 1; } else if (st->l_paddle.score != tm_now->tm_min) { /* r paddle must score */ st->l_paddle.wait = 1; } } erase_ball(st); st->ball.x += st->bx; st->ball.y += st->by; if (!st->clock) { /* in non-clock mode, occasionally increase ball speed */ if ((random()%40)==0) { if (st->bx>0) st->bx++; else st->bx--; } } p_logic(st, &st->r_paddle); p_logic(st, &st->l_paddle); p_hit_top_bottom(&st->r_paddle); p_hit_top_bottom(&st->l_paddle); hit_top_bottom(st); hit_paddle(st); #ifdef OUTPUT_POS printf("(%d,%d,%d,%d)\n",st->ball.x,st->ball.y,st->ball.w,st->ball.h); #endif paint_score(st); paint_net(st); if (1) { paint_paddle(st, &st->r_paddle); paint_paddle(st, &st->l_paddle); } if (1) paint_ball(st); analogtv_reception_update(&st->reception); analogtv_draw(st->tv, st->noise, &reception, 1); now = double_time(); timedelta = (1 / 29.97) - (now - then); return timedelta > 0 ? timedelta * 1000000 : 0; } static const char *pong_defaults [] = { ".background: black", ".foreground: white", "*speed: 6", "*noise: 0.04", "*clock: false", "*p1: ai", "*p2: ai", ANALOGTV_DEFAULTS 0 }; static XrmOptionDescRec pong_options [] = { { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-noise", ".noise", XrmoptionSepArg, 0 }, { "-clock", ".clock", XrmoptionNoArg, "true" }, { "-p1", ".p1", XrmoptionSepArg, 0 }, { "-p2", ".p2", XrmoptionSepArg, 0 }, ANALOGTV_OPTIONS { 0, 0, 0, 0 } }; static void pong_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { struct state *st = (struct state *) closure; XWindowAttributes xgwa; analogtv_reconfigure (st->tv); XGetWindowAttributes(dpy, window, &xgwa); /* AnalogTV does this too. */ xgwa.width = w; xgwa.height = h; do_shape(st, &xgwa); } static Bool pong_event (Display *dpy, Window window, void *closure, XEvent *event) { struct state *st = (struct state *) closure; switch (event->type) { case MotionNotify: st->mouse_y = event->xmotion.y; break; # ifndef HAVE_JWXYZ case FocusIn: if (needs_grab(st)) { grab_pointer(st); } break; case FocusOut: if (needs_grab(st)) { XUngrabPointer (dpy, CurrentTime); st->is_focused = False; } break; # endif /* !HAVE_JWXYZ */ case KeyPress: case KeyRelease: { char c; KeySym key; XLookupString(&event->xkey, &c, 1, &key, 0); Bool is_pressed = event->type == KeyPress; switch(key) { case XK_Up: if (st->l_paddle.player == PLAYER_KEYBOARD || st->r_paddle.player == PLAYER_KEYBOARD) { st->key_up = is_pressed; return True; } break; case XK_Down: if (st->l_paddle.player == PLAYER_KEYBOARD || st->r_paddle.player == PLAYER_KEYBOARD) { st->key_down = is_pressed; return True; } break; case 'w': if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT || st->r_paddle.player == PLAYER_KEYBOARD_LEFT) { st->key_w = is_pressed; return True; } break; case 's': if (st->l_paddle.player == PLAYER_KEYBOARD_LEFT || st->r_paddle.player == PLAYER_KEYBOARD_LEFT) { st->key_s = is_pressed; return True; } break; } } /* Allow the user to pick up and drag either paddle with the mouse, even when not in a mouse-paddle mode. */ case ButtonPress: if (st->dragging != 0) return False; else if (event->xbutton.x < st->w * 0.2) { if (st->l_paddle.player != PLAYER_MOUSE) st->dragging = 1; return True; } else if (event->xbutton.x > st->w * 0.8) { if (st->r_paddle.player != PLAYER_MOUSE) st->dragging = 2; return True; } break; case ButtonRelease: if (st->dragging != 0) { st->dragging = 0; return True; } break; } return False; } static void pong_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; analogtv_release(st->tv); free(st->inp); if (st->score_font.text_im) XDestroyImage (st->score_font.text_im); free (st); } XSCREENSAVER_MODULE ("Pong", pong)