""" pong.py An implementation of pong[1] using Zelle's graphics library[2]. [1] https://en.wikipedia.org/wiki/Pong [2] https://mcsp.wartburg.edu/zelle/python/ Jim Mahoney | cs.marlboro.college | MIT License | Nov 2019 """ from graphics import GraphWin, Rectangle, Circle, Point, _root from time import sleep import os import sys # global constants (using the CONSTANTS_IN_CAPITALS convention) # (Sizes and positions are in pixels. # Coords have x=0 at left, y=0 at top.) WIDTH = 1000 HEIGHT = 800 BORDER = 100 BALL_RADIUS = 50 BALL_BORDER_WIDTH = 7 BALL_X_INIT = WIDTH // 2 BALL_Y_INIT = HEIGHT // 2 PADDLE_HEIGHT = 200 PADDLE_WIDTH = 20 PADDLE_BORDER_WIDTH = 7 PADDLE_X_INIT = WIDTH - 2 * BORDER PADDLE_Y_INIT = HEIGHT // 2 PADDLE_SPEED = 5.0 BALL_COLOR = 'blue' BALL_BORDER_COLOR = 'black' PADDLE_COLOR = 'cyan' PADDLE_BORDER_COLOR = 'black' TICK = 0.01 # time per event loop, in seconds BALL_V_X_INIT = 4.0 # initial velocity in pixels per tick BALL_V_Y_INIT = 3.0 class Ball: """ A ball is a circle in a window """ def __init__(self, window): self.window = window self.v_x = BALL_V_X_INIT self.v_y = BALL_V_Y_INIT self.circle = Circle(Point(BALL_X_INIT, BALL_Y_INIT), BALL_RADIUS) self.circle.setFill(BALL_COLOR) self.circle.setWidth(BALL_BORDER_WIDTH) self.circle.setOutline(BALL_BORDER_COLOR) self.draw() def draw(self): """ Draw the ball in its window """ self.circle.draw(self.window) def bounds(self): """ return (left, right, top, bottom) for bounding rectangle """ return (self.circle.p1.x, self.circle.p2.x, self.circle.p1.y, self.circle.p2.y) def bounce_walls(self): """ Reverse the x or y velocity if the ball hits a wall """ # circle bounding rectangle ... this is *not* quite in Zelle's API. # I found these by using dir() to look at circle object. (left, right, top, bottom) = self.bounds() # collision with top or bottom? if top <= BORDER or bottom >= HEIGHT - BORDER: self.v_y = - self.v_y # collision with left or right? if left <= BORDER : self.v_x = - self.v_x if right >= WIDTH - BORDER: ## # os.system("say you lose") # mac only; google "python beep" # sys.exit() self.v_x = - self.v_x def bounce_paddle(self, paddle): """ Bounce ball if it hits the paddle """ (left_ball, right_ball, top_ball, bottom_ball) = self.bounds() (left_padd, right_padd, top_padd, bottom_padd) = paddle.bounds() if right_ball > left_padd and \ top_ball > top_padd and \ bottom_ball < bottom_padd : self.v_x = - self.v_x def update(self): """ Change ball's velocity if it hits something and move it forward one time tick """ self.bounce_walls() self.circle.move(int(self.v_x), int(self.v_y)) class Paddle: """ user moveable paddle to bounce the ball """ def __init__(self, window): self.window = window self.rectangle = Rectangle(Point(PADDLE_X_INIT - PADDLE_WIDTH // 2, PADDLE_Y_INIT - PADDLE_HEIGHT // 2), Point(PADDLE_X_INIT + PADDLE_WIDTH // 2, PADDLE_Y_INIT + PADDLE_HEIGHT // 2) ) self.rectangle.setFill(PADDLE_COLOR) self.rectangle.setWidth(PADDLE_BORDER_WIDTH) self.rectangle.setOutline(PADDLE_BORDER_COLOR) self.speed = 0 self.draw() def bounds(self): """ return (left, right, top, bottom) for bounding rectangle """ return (self.rectangle.p1.x, self.rectangle.p2.x, self.rectangle.p1.y, self.rectangle.p2.y) def draw(self): """ Draw the paddle in its window """ self.rectangle.draw(self.window) def update(self): """ Move the paddle vertically using arrow keys """ key = self.window.checkKey() if key == 'Up': self.speed = - PADDLE_SPEED elif key == 'Down': self.speed = PADDLE_SPEED elif key: self.speed = 0 self.rectangle.move(0, self.speed) class Screen: """ The pong game screen and its components """ def __init__(self): self.window = GraphWin('Pong!', WIDTH, HEIGHT) self.box = self.make_box() self.ball = Ball(self.window) self.paddle = Paddle(self.window) def make_box(self): """ Create and return interior 'box' within screen that the ball moves in """ box = Rectangle(Point(BORDER, BORDER), Point(WIDTH - BORDER, HEIGHT - BORDER)) box.setFill('grey') box.draw(self.window) return box def run(self): """ The pong event loop """ while True: sleep(TICK) self.ball.bounce_paddle(self.paddle) for thing in (self.ball, self.paddle): thing.update() def main(): Screen().run() if __name__ == '__main__': import doctest doctest.testmod() main()