Sunday, December 13, 2009

Breakout in python for $100

After I posted myself to craigslist, someone emailed me offering to pay me $100 bucks to do their computer assignment. Needless to say, I accepted. The assignment was to create a breakout clone using pygame – the code and thoughts on it are below.

import math import random from pygame import * #graphics, position and size constants BACKGROUND_COLOR = Color("#99B3FF") PADDLE_COLOR = Color("#525252") BALL_COLOR = Color("black") WINDOW_X = 800 #size of the window WINDOW_Y = 600 GRID_X = 20 #size of the grid of bricks GRID_Y = 20 BRICK_SPACE = 5 #space around each brick BRICK_X = (WINDOW_X / GRID_X) - BRICK_SPACE #size of each brick BRICK_Y = (WINDOW_Y / GRID_Y) - BRICK_SPACE BRICK_ANGLE = math.degrees(math.atan(float(BRICK_X)/float(BRICK_Y))) #for collision BRICK_ANGLE_TOPLEFT = -90 - BRICK_ANGLE #for collision BRICK_ANGLE_TOPRIGHT = -90 + BRICK_ANGLE #for collision BRICK_ANGLE_BOTTOMLEFT = 90 + BRICK_ANGLE #for collision BRICK_ANGLE_BOTTOMRIGHT = 90 - BRICK_ANGLE #for collision PADDLE_LENGTH = 100 #size of the paddle PADDLE_WIDTH = 5 PADDLE_AIM_LENGTH = 40 #size of line to help the player aim the ball BALL_R = 10 #size of the ball #control sensitivity constants FRAMERATE = 100 #number of times to run the loop each second PADDLE_MOVE = 10 #amount to move the paddle left or right each loop PADDLE_AIM_MOVE = 5 #amount to angle the pointer up or down each loop BALL_LAUNCH_VEL = 5 #velocity of the ball - should stay constant PADDLE_SKEW = .1 #the amount that the ball will skew to the side when it hits the edges of the paddle #position variables paddle_pos_x = 0 ball_pos_x = 0 ball_pos_y = 0 ball_dx = 0 ball_dy = 0 ball_rect = Rect(0, 0, 0, 0) colliding_with_paddle = False launch_angle = 0 bricks = [] class Brick: alive = True def __init__(self, color, rect): self.color = color self.rect = rect def update(self, surface): if self.alive: draw.rect(surface, self.color, self.rect) #build the brick from a mapfile def build_brick_map(mapfile): bricks = [] color_scheme = {} f_scheme = open(mapfile + '.scheme', 'r') for line in f_scheme: color_scheme[int(line[0])] = Color(line[2:-1]) f_scheme.close() f_map = open(mapfile, 'r').readlines() if len(f_map) > GRID_Y: #allows the map to be smaller than the grid size, making it empty on the bottom print "map file wrong y size" return 0 ypos = 0 for line in f_map: if (len(line) - 1) != GRID_X: print "map file wrong x size" return 0 xpos = 0 for b in line[:-1]: b = int(b) if b > 0: rect = Rect(xpos + BRICK_SPACE, ypos + BRICK_SPACE, BRICK_X, BRICK_Y) bricks.append(Brick(color_scheme[b], rect)) xpos += BRICK_X + BRICK_SPACE ypos += BRICK_Y + BRICK_SPACE return bricks #game constants LEVELS = ["level1", "level2", "level3"] DEFAULT_NUM_LIVES = 3 #number of lives the player starts with STARTING_LEVEL = 0 #game variables score = 0 current_level = 0 lives = 0 launch_mode = False new_game = True new_level = True init() screen = display.set_mode((WINDOW_X, WINDOW_Y)) drawing_surface = Surface(screen.get_size()).convert() overlay = Surface(screen.get_size(), SRCALPHA).convert_alpha() small_overlay_font = font.Font("V5PRC___.TTF", 36) big_overlay_font = font.Font("V5PRC___.TTF", 72) overlay_text = "" overlay_sub = "" clock = time.Clock() quit = False paused = False while not quit: clock.tick(FRAMERATE) #reset objects if restarting or a new level if new_game: score = 0 lives = DEFAULT_NUM_LIVES current_level = STARTING_LEVEL new_level = True new_game = False if new_level: bricks = build_brick_map(LEVELS[current_level]) if bricks == 0 or len(bricks) == 0: print "couldn't build the brick map!" quit = True break paddle_pos_x = (WINDOW_X/2) launch_mode = True launch_angle = random.randint(15, 165) #discourage players shooting straight up new_level = False #manage input space_pressed = False left_pressed = False right_pressed = False up_pressed = False down_pressed = False for e in event.get(): if e.type == QUIT: quit = True break if e.type == KEYDOWN and e.key == K_SPACE: #space as an event so that it is only registered once per keypress space_pressed = True key_input = key.get_pressed() if key_input[K_LEFT]: #this, on the other hand, registers if the key is down every loop left_pressed = True if key_input[K_RIGHT]: right_pressed = True if key_input[K_UP]: up_pressed = True if key_input[K_DOWN]: down_pressed = True overlay_text = "" overlay_sub = "" if paused: overlay_text = "paused" overlay_sub = "press space to continue" #check if game or level is over if lives == 0: #game over paused = True overlay_text = "game over" if space_pressed: paused = False new_game = True elif len(bricks) == 0: paused = True if current_level + 1 == len(LEVELS): #game over overlay_text = "you win!" overlay_sub = "press space to quit" if space_pressed: quit= True break else: #next level overlay_text = "level " + str(current_level + 2) overlay_sub = "press space to continue" if space_pressed: paused = False current_level += 1 new_level = True elif space_pressed: if launch_mode: ball_dx = -math.cos(math.radians(launch_angle)) * BALL_LAUNCH_VEL ball_dy = -math.sin(math.radians(launch_angle)) * BALL_LAUNCH_VEL launch_mode = False else: paused = not paused #main loop if not paused: #move everything if left_pressed and paddle_pos_x > PADDLE_LENGTH/2: paddle_pos_x -= PADDLE_MOVE if right_pressed and paddle_pos_x < WINDOW_X - PADDLE_LENGTH/2: paddle_pos_x += PADDLE_MOVE if launch_mode: ball_pos_x = paddle_pos_x ball_pos_y = WINDOW_Y - PADDLE_WIDTH - BALL_R if up_pressed and launch_angle <= 165: launch_angle += PADDLE_AIM_MOVE if down_pressed and launch_angle >= 15: launch_angle -= PADDLE_AIM_MOVE else: ball_pos_x += ball_dx ball_pos_y += ball_dy ball_rect = Rect(ball_pos_x - BALL_R, ball_pos_y - BALL_R, 2*BALL_R, 2*BALL_R) #check for collisions if ball_dx != 0 or ball_dy != 0: if ball_pos_y > (WINDOW_Y - BALL_R - PADDLE_WIDTH) and \ math.fabs(ball_pos_x - paddle_pos_x) < (PADDLE_LENGTH/2): if not colliding_with_paddle: ball_dy = -ball_dy ball_dx += (ball_pos_x - paddle_pos_x) * PADDLE_SKEW colliding_with_paddle = True elif ball_pos_y > (WINDOW_Y - PADDLE_WIDTH) and \ math.fabs(ball_pos_x - paddle_pos_x) < ((PADDLE_LENGTH/2) + BALL_R): #bounce off the side of the paddle if not colliding_with_paddle: ball_dx = -ball_dx colliding_with_paddle = True else: colliding_with_paddle = False if ball_pos_y < BALL_R: ball_dy = -ball_dy elif ball_pos_y > (WINDOW_Y + BALL_R): lives -= 1 launch_mode = True if ball_pos_x < BALL_R or ball_pos_x > (WINDOW_X - BALL_R): ball_dx = -ball_dx for b in bricks: if ball_rect.colliderect(b.rect): b.alive = False #check which side it collided with theta = math.degrees(math.atan2(ball_pos_y - b.rect.center[1], ball_pos_x - b.rect.center[0])) if (theta > BRICK_ANGLE_TOPLEFT and theta < BRICK_ANGLE_TOPRIGHT) or (theta < BRICK_ANGLE_BOTTOMLEFT and theta > BRICK_ANGLE_BOTTOMRIGHT): #top or bottom ball_dy = -ball_dy else: ball_dx = -ball_dx bricks.remove(b) score += 1 break #draw ball, bricks, paddle drawing_surface.fill(BACKGROUND_COLOR) draw.rect(drawing_surface, PADDLE_COLOR, Rect((paddle_pos_x - PADDLE_LENGTH/2), (WINDOW_Y - PADDLE_WIDTH), PADDLE_LENGTH, PADDLE_WIDTH)) if launch_mode: x = ball_pos_x - math.cos(math.radians(launch_angle)) * PADDLE_AIM_LENGTH y = ball_pos_y - math.sin(math.radians(launch_angle)) * PADDLE_AIM_LENGTH draw.line(drawing_surface, Color("black"), (ball_pos_x, ball_pos_y), (x, y)) draw.circle(drawing_surface, BALL_COLOR, (ball_pos_x, ball_pos_y), BALL_R) for b in bricks: b.update(drawing_surface) #draw overlay if paused: overlay.fill(Color(0, 0, 0, 200)) text_color = Color("white") else: overlay.fill(Color(0, 0, 0, 0)) text_color = Color("black") o1 = big_overlay_font.render(overlay_text, True, text_color) o2 = small_overlay_font.render(overlay_sub, True, text_color) if lives != 1: lives_text = str(lives) + " lives" else: lives_text = "1 life" o3 = small_overlay_font.render(lives_text, True, text_color) o4 = small_overlay_font.render("score " + str(score), True, text_color) overlay.blit(o1, ((WINDOW_X - o1.get_size()[0])/2, WINDOW_Y/3)) overlay.blit(o2, ((WINDOW_X - o2.get_size()[0])/2, WINDOW_Y/3 + o1.get_size()[1] + 10)) overlay.blit(o3, (20, WINDOW_Y - o3.get_size()[1] - 10)) overlay.blit(o4, (WINDOW_X - o4.get_size()[0] - 20, WINDOW_Y - o4.get_size()[1] - 10)) screen.blit(drawing_surface, (0, 0)) screen.blit(overlay, (0, 0)) display.flip()

[Via http://structwhogrewup.wordpress.com]

No comments:

Post a Comment