# Implementation of classic arcade game Pong
try:
import simplegui
except ModuleNotFoundError:
import simplequi as simplegui
import random
# initialize globals - pos and vel encode vertical info for paddles
WIDTH = 600
HEIGHT = 400
BALL_RADIUS = 20
PAD_WIDTH = 8
PAD_HEIGHT = 80
HALF_PAD_WIDTH = PAD_WIDTH / 2
HALF_PAD_HEIGHT = PAD_HEIGHT / 2
LEFT = False
RIGHT = True
FRAME_RATE = 60
PADDLE_VEL = 100
BALL_VEL_INCR_FACTOR = 1.1
SCORE_FONT_SIZE = 50
SCORE_OFFSET_X_FROM_MIDDLE = 30
SCORE_OFFSET_Y = 60
BALL_COLOR_GREEN_INITIAL = 255
BALL_COLOR_GREEN_INCR = -30
# this value is added to the green channel after every paddle strike
score1 = score2 = 0
ball_pos = []
ball_vel = [] # unit: pixels per frame update
paddle1_pos = paddle2_pos = 0
paddle1_vel = paddle2_vel = 0
# paddles have only vertical position and velocity
# their horizontal pos/vel is implicit
ball_color_green = 0
# initialize ball_pos and ball_vel for new bal in middle of table
# if direction is RIGHT, the ball's velocity is upper right, else upper left
def spawn_ball(direction):
global ball_pos, ball_vel # these are vectors stored as lists
global ball_color_green
ball_pos = [WIDTH/2, HEIGHT/2]
ball_color_green = BALL_COLOR_GREEN_INITIAL
if direction == RIGHT:
x_vel_sign = 1
else:
x_vel_sign = -1
ball_vel = [
x_vel_sign * random.randrange(120, 140),
- random.randrange(60, 180)
]
# define event handlers
def new_game():
global paddle1_pos, paddle2_pos, paddle1_vel, paddle2_vel
# these are numbers
global score1, score2 # these are ints
global ball_color_green
paddle1_pos = paddle2_pos = HEIGHT/2
paddle1_vel = paddle2_vel = 0
score1 = score2 = 0
ball_color_green = BALL_COLOR_GREEN_INITIAL
spawn_ball(RIGHT)
def draw(canvas: simplegui.Canvas):
global score1, score2, paddle1_pos, paddle2_pos, ball_pos, ball_vel
# draw mid line and gutters
canvas.draw_line([WIDTH / 2, 0], [WIDTH / 2, HEIGHT], 1, "White")
canvas.draw_line([PAD_WIDTH, 0], [PAD_WIDTH, HEIGHT], 1, "White")
canvas.draw_line(
[WIDTH-PAD_WIDTH, 0], [WIDTH-PAD_WIDTH, HEIGHT], 1, "White")
# update ball
# vertical collision check
if ball_pos[1] <= 0+BALL_RADIUS or ball_pos[1] >= HEIGHT-BALL_RADIUS:
ball_vel[1] *= -1
# gutter and paddle collision check
def paddle_collision_check(player_side: bool):
global score1, score2, ball_color_green
if player_side == LEFT:
touched_paddle = ball_pos[1] <= paddle1_pos + HALF_PAD_HEIGHT and \
ball_pos[1] >= paddle1_pos - HALF_PAD_HEIGHT
else:
touched_paddle = ball_pos[1] <= paddle2_pos + HALF_PAD_HEIGHT and \
ball_pos[1] >= paddle2_pos - HALF_PAD_HEIGHT
if touched_paddle:
# reflect the ball and increase velocity
ball_vel[0] *= -1 * BALL_VEL_INCR_FACTOR
ball_vel[1] *= BALL_VEL_INCR_FACTOR
# NOTE
# the velocity and its x, y components build up a triangle.
# The velocity is the hypotenuse. If we scale the hypotenuse of
# the triangle by the factor f, then x and y will be scaled by the
# same factor
# adjust ball color
ball_color_green += BALL_COLOR_GREEN_INCR
if ball_color_green >= 255:
ball_color_green = 255
elif ball_color_green <= 0:
ball_color_green = 0
else:
if player_side == LEFT:
spawn_ball(RIGHT)
score2 += 1
else:
spawn_ball(RIGHT)
score1 += 1
if ball_pos[0] <= PAD_WIDTH+BALL_RADIUS: # left gutter
paddle_collision_check(LEFT)
elif ball_pos[0] >= WIDTH-PAD_WIDTH-BALL_RADIUS: # right gutter
paddle_collision_check(RIGHT)
ball_pos[0] += ball_vel[0] / FRAME_RATE
ball_pos[1] += ball_vel[1] / FRAME_RATE
# draw ball
# green channel is dynamic
ball_color = f'#ff{ball_color_green:x}00'
canvas.draw_circle(
ball_pos,
BALL_RADIUS,
2,
ball_color,
ball_color)
# update paddle's vertical position, keep paddle on the screen
paddle1_pos += paddle1_vel / FRAME_RATE
paddle2_pos += paddle2_vel / FRAME_RATE
def update_and_draw_paddle(canvas: simplegui.Canvas, player_side: bool):
paddle_pos = paddle1_pos if player_side == LEFT else paddle2_pos
# update paddle
if paddle_pos <= HALF_PAD_HEIGHT:
paddle_pos = HALF_PAD_HEIGHT
elif paddle_pos >= HEIGHT-HALF_PAD_HEIGHT:
paddle_pos = HEIGHT-HALF_PAD_HEIGHT
# draw paddle
x = HALF_PAD_WIDTH if player_side == LEFT else WIDTH-HALF_PAD_WIDTH
canvas.draw_line(
[x, paddle_pos-HALF_PAD_HEIGHT],
[x, paddle_pos+HALF_PAD_HEIGHT],
PAD_WIDTH,
'yellow',
)
update_and_draw_paddle(canvas, LEFT)
update_and_draw_paddle(canvas, RIGHT)
# draw scores
text = f'{score1} {score2}'
textwidth = frame.get_canvas_textwidth(text, SCORE_FONT_SIZE, 'monospace')
canvas.draw_text(
text,
[WIDTH/2-textwidth/2, SCORE_OFFSET_Y],
SCORE_FONT_SIZE,
'gray',
'monospace')
def keydown(key):
global paddle1_vel, paddle2_vel
if key == simplegui.KEY_MAP['w']:
paddle1_vel = -PADDLE_VEL
elif key == simplegui.KEY_MAP['s']:
paddle1_vel = PADDLE_VEL
elif key == simplegui.KEY_MAP['up']:
paddle2_vel = -PADDLE_VEL
elif key == simplegui.KEY_MAP['down']:
paddle2_vel = PADDLE_VEL
def keyup(key):
global paddle1_vel, paddle2_vel
paddle1_vel = paddle2_vel = 0
# create frame
frame = simplegui.create_frame("Pong", WIDTH, HEIGHT)
frame.set_draw_handler(draw)
frame.set_keydown_handler(keydown)
frame.set_keyup_handler(keyup)
frame.add_button('Reset', new_game)
# start frame
new_game()
frame.start()