Last active
December 17, 2015 08:08
-
-
Save boxmein/5577946 to your computer and use it in GitHub Desktop.
A Python implementation of Pong for "Interactive Programming in Python" in Coursera.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Requires to be played back in CodeSkulptor since | |
# the course requires 'simplegui' | |
# and the only place where it's implemented is ... guess it... | |
# CodeSkulptor! | |
# http://codeskulptor.org/ for CodeSkulptor | |
# http://www.codeskulptor.org/#user13_lPBkTAO6Ct_31.py for my script precisely | |
# the digits at the end of the URL may be subject to change (increment by one ad nauseum) | |
# -- 14. May 2013 | |
# Implementation of classic arcade game Pong | |
import simplegui | |
import random | |
import math | |
# initialize globals - pos and vel encode vertical info for paddles | |
WIDTH = 600 # Game area width. (Canvas width) | |
HEIGHT = 400 # Game area height. (Canvas height) | |
BALL_RADIUS = 10 # Radius of the ball in play. Note, not a diameter. | |
PAD_WIDTH = 8 # Width of the game paddle. | |
PAD_HEIGHT = 80 # Height of the game paddle. | |
DEBUG = True # Turn on debugging lines and variable outputting. | |
#DEBUG = False | |
#MAGICAL = True # what is this? | |
MAGICAL = False | |
RIGHT_GUTTER = WIDTH-PAD_WIDTH # Gutter = the area beyond the paddle. | |
LEFT_GUTTER = PAD_WIDTH # Left gutter is simple enough. | |
ball_pos = [WIDTH/2,HEIGHT/2] # Ball starts out in the center. | |
ball_vel = [0, 0] # Ball velocity in multipliers (-1, 1) rather than absolute speed. | |
keys = simplegui.KEY_MAP # For the sake of conciseness. | |
playing = False # The game can be paused and will be paused after each match. | |
paddle1_pos = 0 # Left paddle's Y position | |
paddle2_pos = 0 # Right paddle's Y position | |
paddle1_vel = 0 # For me to be able to update paddles in the same function as everything else. | |
paddle2_vel = 0 # Again, in multipliers rather than absolute speed. | |
score1 = 0 # Score variables. Left paddle here. | |
score2 = 0 # Right paddle here :) | |
maxspeed = 2 # Try setting this to more than 10. I dare you. | |
default_maxspeed = 2 # Speed is reset to 2 by this. | |
SPEED_INCREMENT = 0.1 # Speed is incremented by 10% every paddle touch :L | |
do_speed_increment = True # If we use speed incrementing, required for the course. | |
next_player = random.randint(0, 1) == 0 # First player is randomly picked. | |
## Sets ball initial velocity. | |
def ball_init(right): | |
global ball_pos, ball_vel | |
ball_vel[1] = -1 | |
if right: | |
ball_vel[0] = 1 | |
else: | |
ball_vel[0] = -1 | |
## Game update function. Not drawing! | |
def update(c): | |
global score1, score2, paddle1_pos, paddle2_pos, ball_pos, ball_vel, playing | |
global paddle1_vel, paddle2_vel, maxspeed | |
## This is where it gets crazy. | |
## Paddle bounds checks | |
if paddle1_pos < 0: paddle1_pos = 0 | |
if paddle2_pos < 0: paddle2_pos = 0 | |
if paddle1_pos+PAD_HEIGHT >= HEIGHT: paddle1_pos = HEIGHT-PAD_HEIGHT-1 | |
if paddle2_pos+PAD_HEIGHT >= HEIGHT: paddle2_pos = HEIGHT-PAD_HEIGHT-1 | |
# ?!?!? | |
if MAGICAL: | |
paddle2_vel = stupidai( paddle2_pos+PAD_HEIGHT/2,ball_pos[1]) | |
## Paddle positioning and placement | |
paddle1_pos = paddle1_pos + maxspeed * paddle1_vel | |
paddle2_pos = paddle2_pos + maxspeed * paddle2_vel | |
## Ball: | |
## 1. Bounds checks (red side walls) | |
if (ball_pos[1] - BALL_RADIUS <= 0 or | |
ball_pos[1] + BALL_RADIUS >= HEIGHT): | |
ball_vel[1] = -ball_vel[1] | |
## 2. Paddle bounce. | |
if ((ball_pos[1] >= paddle2_pos and | |
ball_pos[1] < paddle2_pos+PAD_HEIGHT and | |
ball_pos[0] + BALL_RADIUS >= RIGHT_GUTTER) | |
or | |
(ball_pos[1] >= paddle1_pos and | |
ball_pos[1] < paddle1_pos+PAD_HEIGHT and | |
ball_pos[0] - BALL_RADIUS <= LEFT_GUTTER)): | |
ball_vel[0] = -ball_vel[0] | |
if do_speed_increment: | |
maxspeed *= 1+SPEED_INCREMENT | |
## 2.5 Missing the paddle. Reset ball! | |
## Separately for each paddle to set the winner of a game. | |
elif ball_pos[0] + BALL_RADIUS >= RIGHT_GUTTER: | |
# paddle1 win! | |
resetgame(1) | |
elif ball_pos[0] - BALL_RADIUS <= LEFT_GUTTER: | |
# paddle2 win! | |
resetgame(2) | |
## 3. Move the ball | |
ball_pos[0] = ball_pos[0] + maxspeed * ball_vel[0] | |
ball_pos[1] = ball_pos[1] + maxspeed * ball_vel[1] | |
def draw(c): | |
global score1, score2, paddle1_pos, paddle2_pos, ball_pos, ball_vel | |
## There's a pause system now :) | |
if playing: | |
update(c) | |
## Gutter/Middle lines | |
c.draw_line([WIDTH / 2, 0],[WIDTH / 2, HEIGHT], 1, "White") | |
c.draw_line([PAD_WIDTH, 0],[PAD_WIDTH, HEIGHT], 1, "White") | |
c.draw_line([RIGHT_GUTTER, 0],[RIGHT_GUTTER, HEIGHT], 1, "White") | |
## Left paddle (manual rectangles <3) | |
c.draw_polygon( | |
[(0, paddle1_pos), | |
(LEFT_GUTTER,paddle1_pos), | |
(LEFT_GUTTER,paddle1_pos+PAD_HEIGHT), | |
(0, paddle1_pos+PAD_HEIGHT)], | |
1, "White", "White") | |
## Right paddle | |
c.draw_polygon([(WIDTH, paddle2_pos), | |
(RIGHT_GUTTER, paddle2_pos), | |
(RIGHT_GUTTER, paddle2_pos+PAD_HEIGHT), | |
(WIDTH, paddle2_pos+PAD_HEIGHT)], | |
1, "White", "White") | |
# draw ball and scores | |
c.draw_circle(ball_pos, BALL_RADIUS, 1, "white", "white") | |
c.draw_text("%d %d" % (score1, score2), (WIDTH//2-50,HEIGHT), 48, "silver", "sans-serif") | |
if not playing: | |
c.draw_text("Click to start game.", (100, 100), 48, "silver", "sans-serif") | |
# Debug | |
if DEBUG: | |
# Informational texts. | |
c.draw_text("Paddle 1 Y: %d V: %d" % (paddle1_pos,paddle1_vel), (10,12), 12, "gray", "sans-serif") | |
c.draw_text("Paddle 2 Y: %d V: %d" % (paddle2_pos,paddle2_vel), (10, 24), 12, "gray", "sans-serif") | |
c.draw_text("Ball X: %d Y: %d, Vx: %d Vy: %d" % (ball_pos[0],ball_pos[1], ball_vel[0], ball_vel[1]), (10,36), 12, "gray", "sans-serif") | |
c.draw_text("score1: %d score2: %d" % (score1, score2), (10,48), 12, "gray", "sans-serif") | |
c.draw_text("game speed: %d" % maxspeed, (10,60), 12, "gray", "sans-serif") | |
c.draw_text("playing: %s" % playing, (10,72), 12, "gray", "sans-serif") | |
# Side collision lines. | |
c.draw_line((0, BALL_RADIUS), (WIDTH, BALL_RADIUS), 1, "red") | |
c.draw_line((0, HEIGHT-BALL_RADIUS), (WIDTH, HEIGHT-BALL_RADIUS), 1, "red") | |
# Paddle collision lines. | |
c.draw_line((LEFT_GUTTER+BALL_RADIUS, 0), (LEFT_GUTTER+BALL_RADIUS, HEIGHT), 1, "red") | |
c.draw_line((RIGHT_GUTTER-BALL_RADIUS, 0), (RIGHT_GUTTER-BALL_RADIUS, HEIGHT), 1, "red") | |
# Okay paddle collision lines | |
c.draw_line((LEFT_GUTTER+BALL_RADIUS, paddle1_pos), (LEFT_GUTTER+BALL_RADIUS, paddle1_pos+PAD_HEIGHT), 1, "green") | |
c.draw_line((RIGHT_GUTTER-BALL_RADIUS, paddle2_pos), (RIGHT_GUTTER-BALL_RADIUS, paddle2_pos+PAD_HEIGHT), 1, "green") | |
def keydown(key): | |
global paddle1_vel, paddle2_vel, playing | |
if key == keys["up"]: | |
# Right paddle UP! | |
paddle2_vel = -1 | |
elif key == keys["down"]: | |
# Right paddle DOWN! | |
paddle2_vel = 1 | |
elif key == keys["w"]: | |
# Left paddle UP! | |
paddle1_vel = -1 | |
elif key == keys["s"]: | |
paddle1_vel = 1 | |
# else nothing >_> | |
def keyup(key): | |
global paddle1_vel, paddle2_vel, playing | |
if key == keys["up"] or key == keys["down"]: | |
paddle2_vel = 0 | |
elif key == keys["w"] or key == keys["s"]: | |
paddle1_vel = 0 | |
## Added them here to prevent infinite key spam | |
elif key == keys["space"]: | |
if playing: | |
pausegame() | |
else: | |
startgame() | |
elif key == keys["r"]: | |
playing = False | |
startgame() | |
# else nothing | |
# Optional argument, because called by mouseclick handler. | |
def startgame(a = 0): | |
global playing | |
if a: | |
resetgame() | |
# unpause! | |
if not playing: | |
playing = True | |
## Pauses the game. | |
def pausegame(): | |
global playing | |
if playing: | |
playing = False | |
## Simply restarts the game. | |
def resetgame(winner=0): | |
global ball_pos, ball_vel, playing | |
global score1, score2, next_player | |
global maxspeed, default_maxspeed, do_speed_increment | |
ball_pos = [WIDTH/2, HEIGHT/2] | |
ball_vel = [0, 0] | |
## Resets max speed. | |
if do_speed_increment: | |
maxspeed = default_maxspeed | |
playing = False | |
if winner == 0: | |
# Reset button clicked! | |
ball_init(next_player) | |
startgame() | |
## Win scores. Might want to use the multiplier here, oh how I want to! | |
elif winner == 1: | |
score1 += 1 | |
# is the next player the right one? no! | |
next_player = False | |
elif winner == 2: | |
score2 += 1 | |
# is the next player the right one? yes! | |
next_player = True | |
def reset_game(a = 0): | |
global score1, score2 | |
score1 = score2 = 0 | |
resetgame(0) | |
## Nothing to see here, move along | |
stupidai = lambda y1,y2: 1 if y1 < y2 else -1 | |
### create frame | |
frame = simplegui.create_frame("Pong", WIDTH, HEIGHT) | |
frame.add_button("Start", startgame, 50) | |
frame.add_button("Pause", pausegame, 50) | |
frame.add_button("Reset", reset_game, 50) | |
## Some keys to use. | |
frame.add_label ("Pong") | |
frame.add_label ("Up/down arrow for right paddle.") | |
frame.add_label ("W/S for left paddle.") | |
frame.add_label ("Space to pause/unpause.") | |
frame.set_draw_handler(draw) | |
frame.set_keydown_handler(keydown) | |
frame.set_keyup_handler(keyup) | |
frame.set_mouseclick_handler(startgame) | |
# start frame | |
frame.start() | |
resetgame() | |
# Rubric: | |
# 1. The ball spawns in the middle of the canvas with either an upward left or an upward right velocity. | |
# 2. The ball bounces off of the top and bottom walls correctly. | |
# 3. The ball respawns in the middle of the screen when it strikes the left or right gutter but not the paddles | |
# 4. The left and right gutters are properly used as the edges. | |
# 5. The ball spawns moving towards the player that won the last point. | |
# 6. The 'w' and 's' keys correctly control the velocity of the left paddle as described above. | |
# 7. The up and down arrows keys correctly control the velocity of the right paddle | |
# 8. The edge of each paddle is flush with the gutter. | |
# 9. The paddles stay on the canvas at all times | |
# 10. The ball correctly bounces off the left and right paddles. | |
# 11. The scoring text is positioned and updated appropriately. | |
# 12. Reset button that resets the score and restarts the game. | |
# Thanks for grading <3 | |
# Bonus: Set MAGICAL to True to get some idiotic AI on the right side! | |
# And by idiotic I mean it's unbeatable. Literally. | |
# You'll still play with W/S on the left tho. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment