#!/usr/bin/env python ''' Y positions INCREASE in the direction commonly known as "down". To make the game harder: increase FPS, CEILING_FLOOR_HEIGHT, CAVE_STDDEV, FLOATER_SPEED, CAVE_SPEED and/or decrease WALL_TIME and/or incrase or decrease GRAVITY or WIND To make the game easier, do the opposite of the above actions. ''' try: import psyco psyco.profile() except: pass import os, sys, time, random, math try: import pygame from pygame.locals import * except: if 'pythonw' in sys.executable: from Tkinter import * tk = Tk() Label(tk, text='you need pygame to play pycopter.\nget it here:').pack() s = StringVar() s.set('http://pygame.org') Entry(tk, textvariable=s).pack() tk.mainloop() else: print "you need pygame to play this game: http://www.pygame.org/" sys.exit(1) html_color_to_int_tuple = lambda s: tuple([int(s[i:i+2], 16) for i in range(0, 5, 2)]) ############################ Configuration Variables ############################ WIDTH = 500 # width of total game window HEIGHT = 500 # height of game window COPTER_X = WIDTH / 4 # constant horizontal position of copter WALL_TIME = 200 # number of iterations of main loop between introductions of Walls SMOKE_TIME = 15 # number of iterations of main loop between puffs of smoke behind your copter SHIELD_TIME = 400 # number of iterations of main loop shields last WALL_WIDTH, WALL_HEIGHT = 30, 100 SHIELD_THICKNESS = 5 SHIELD_WARN_TIME = 100 # number of main loop iterations before shields run out to turn SHIELD_WARN_COLOR WALL_STDDEV = HEIGHT / 10 # standard deviation of the vertical position of each Wall [raise this to disperse the Walls father] WALL_COLOR = html_color_to_int_tuple('004000') SMOKE_COLOR = html_color_to_int_tuple('888888') BG_COLOR = html_color_to_int_tuple('aed9ff') TEXT_COLOR = html_color_to_int_tuple('4455ff') MISSILE_COLOR = html_color_to_int_tuple('ff0000') POWERUP_COLOR = html_color_to_int_tuple('0000ff') SHIELD_COLOR = html_color_to_int_tuple('00ff00') SHIELD_COUNTER_COLOR = html_color_to_int_tuple('ff0000') SHIELD_WARN_COLOR = html_color_to_int_tuple('ff0000') OWNED_TEXT_COLOR = html_color_to_int_tuple('ff0000') MISSILE_WIDTH, MISSILE_HEIGHT = 30, 10 ENABLE_POWERUPS = True POWERUP_FREQUENCY = .1 # probability of a powerup occuring between any two Walls OWNED_TIME = .15 # number of seconds to display 'OWNED' when you die POWERUP_POSITION = (10, 10) # where powerups are "stored" when you collect them SHIELD_COUNTER_POSITION = (WIDTH / 2), (HEIGHT - 20) DISTANCE_TEXT_POSITION = 10, (HEIGHT - 40) BEST_TEXT_POSITION = (WIDTH - 200), (HEIGHT - 40) BEST_EVER_TEXT_POSITION = (WIDTH / 2), -10 MISSILE_KEY = K_RIGHT # key to fire a missile SHIELD_KEY = K_LEFT # key to raise shields ACTIVATION_KEY = K_UP # key to raise copter PAUSE_KEY = K_RCTRL # key to pause the game QUIT_KEY = K_ESCAPE # key to press to quit the game FULLSCREEN_KEY = K_RSHIFT # key to enter fullscreen mode CAVE_STDDEV = 20 # standard deviation of the length of stalactites/stalagmites CAVE_SPEED = .005 # rate at which the ceiling and floor come together on you CAVE_TIME = 20 # number of main loop iterations between sections of the cave ceiling and floor CEILING_FLOOR_HEIGHT = 40 # height of ceiling and floor at init FONT = ('courier', 26, True) # import pygame, run pygame.init(), then pygame.font.get_fonts() to see a list of available fonts. FPS = 80 # roughly frames per second; raise this for faster gameplay COPTER_IMAGE_FILENAME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'copter.gif') BEST_FILENAME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'highest.bin') COPTER_DISTANCE_RATIO = 10 # "distance" points per number of main event loop iterations FLOATER_SPEED = 1.5 # number of pixels moved by Floaters each iteration of the main event loop GRAVITY = -.07 # vertical acceleration of the copter when you're holding down ACTIVATION_KEY or the first mouse button WIND = .08 # vertical acceleration of the copter when you are NOT holding down ACTIVATION_KEY or the first mouse button ################################################################################# how_to_play = [ "To start, hit up arrow or left-click.", "To rise, hold up arrow or", " left mouse button.", "To go down, release.", "To pause, hit right control.", "To quit, hit escape.", "For fullscreen, hit right shift.", "Blue boxes are powerups", " [good for missiles and shields].", "To fire a missile, hit right arrow.", "To use shields, hit left arrow.", ] def load_image(fullname, colorkey=None): ''' example: self.image, self.rect = load_image(COPTER_IMAGE_FILENAME) ''' try: image = pygame.image.load(fullname) except pygame.error, message: print 'Cannot load image:', fullname raise SystemExit, message image = image.convert() if colorkey is not None: if colorkey is -1: colorkey = image.get_at((0, 0)) image.set_colorkey(colorkey, RLEACCEL) return image, image.get_rect() class Copter(pygame.sprite.Sprite): ''' the class containing an image of a ''' def __init__(self, *a, **kw): ''' load the copter image, position it at ''' pygame.sprite.Sprite.__init__(self) self.image, self.rect = load_image(COPTER_IMAGE_FILENAME) self.area = pygame.display.get_surface().get_rect() self.rect.topleft = COPTER_X, (HEIGHT / 2) self.distance_ratio_counter = 0 self.distance = 0 self.dy = 0 self.powerups = 0 def can_squeeze_through(self, y): ''' return True if y is large enough to contain a wall and 2 copters ''' return y > WALL_HEIGHT + 2 * self.rect.height def update(self): ''' move up or down according to self.dy, increment self.distance. ''' self.rect.centery -= self.dy # y positions INCREASE in the direction commonly known as "down". self.distance_ratio_counter += 1 if self.distance_ratio_counter == 10: self.distance += 1 self.distance_ratio_counter = 0 class Floater(pygame.sprite.Sprite): ''' ''' def __init__(self): ''' ''' pygame.sprite.Sprite.__init__(self) def update(self): ''' move self one pixel to the left ''' self.rect.centerx -= FLOATER_SPEED class Immobile(pygame.sprite.Sprite): ''' ''' def __init__(self, *a): ''' ''' pygame.sprite.Sprite.__init__(self) self.image, self.rect = mk_image_rect(*a) self.image.fill(WALL_COLOR) def update(self): pass def mk_image_rect(w=10, h=10, x=0, y=0): ''' ''' w, h, x, y = map(int, [w, h, x, y]) return pygame.Surface((w, h)), Rect((x, y, w, h)) class Wall(Floater): ''' ''' def __init__(self, *a, **kw): ''' .997 = P(0 <= centery <= HEIGHT) ''' Floater.__init__(self) self.image, self.rect = mk_image_rect(WALL_WIDTH, WALL_HEIGHT, WIDTH, (random.normalvariate(HEIGHT / 2, WALL_STDDEV) - (WALL_HEIGHT / 2))) self.image.fill(WALL_COLOR) class Smoke(Floater): ''' ''' def __init__(self, y=(HEIGHT / 2)): ''' ''' Floater.__init__(self) self.image, self.rect = mk_image_rect(10, 10, COPTER_X, y) self.image.fill(SMOKE_COLOR) class PowerUp(Floater): ''' ''' def __init__(self): ''' ''' Floater.__init__(self) self.image, self.rect = mk_image_rect(10, 10, WIDTH, HEIGHT / 2) self.image.fill(POWERUP_COLOR) class CavePoint(Floater): ''' ''' def __init__(self, **kw): ''' ''' Floater.__init__(self) self.image, self.rect = mk_image_rect( math.ceil(FLOATER_SPEED) * CAVE_TIME, # yes, that .5 is a magic number. kw.get('h', 10), WIDTH, kw.get('y', 0) ) self.image.fill(WALL_COLOR) class Missile(pygame.sprite.Sprite): ''' ''' def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image, self.rect = mk_image_rect(MISSILE_WIDTH, MISSILE_HEIGHT, x, y) self.image.fill(MISSILE_COLOR) def update(self): ''' move self one pixel to the left ''' self.rect.centerx += FLOATER_SPEED def multiline(font, lines, color=TEXT_COLOR, antialias=1): ''' multiline(font, lines) -> Surface Renders multiple lines of text. lines can be a string containing '\n' or a list of strings. ''' if isinstance(lines, str): lines = lines.split("\n") maxwidth = 0 height = 0 for line in lines: w, h = font.size(line) maxwidth = max(maxwidth, w) height += h s = pygame.Surface((maxwidth, height)) s.fill(BG_COLOR) y = 0 for line in lines: if line: s2 = font.render(line, antialias, color) s.blit(s2, (0, y)) y += s2.get_size()[1] else: w, h = font.size(line) y += h return s def rebase(i, frombase=None, tobase=None, fromalphabet=None, toalphabet=None, resize=1, too_big=40000, debug=False): ''' if frombase is not specified, it is guessed from the type and/or char in i with highest ord. tobase defaults to [10, 2][frombase == 10]. the alphabets are map(chr, range(256)) if its base is between 62 and 255; otherwise, string.digits+string.letters. always returns a string which is also valid input. valid bases are ints in range(-255, 256). alphabets must be subscriptable, and can only contain str's. invalid tobases are replied with 'why?'; rebase('why?') == '217648673'. returned string is zfilled to the next largest multiple of resize ''' if frombase == None: if isinstance(i, (int, long)): frombase = 10 elif isinstance(i, str): a = str(i) if True in map(lambda x: (chr(x) in a), range(ord('0')) + range(58, 65) + range(91, 97) + range(123, 256)): frombase = max(map(ord, a)) + 1 else: frombase = max(map((string.digits + string.letters).index, a)) + 1 if tobase == None: tobase = [10, 2][frombase == 10] # got bases, ensuring that everything is an int tobase, frombase = int(tobase), int(frombase) abstobase, absfrombase = int((tobase > 0) and tobase or -tobase), int((frombase > 0) and frombase or -frombase) if absfrombase in [0, 1]: i = len(str(i)) elif 2 <= frombase <= 36: i = int(str(i), frombase) else: i = str(i) n = 0 if fromalphabet == None: if 62 <= absfrombase <= 256: fromalphabet = map(chr, range(256)) else: fromalphabet = string.digits + string.letters fromalphabet = fromalphabet[:absfrombase] for j in range(len(i)): n += (frombase ** j) * fromalphabet.index(i[-1-j]) i = n # got ints, converting to tobase if debug: print 'converting %d from base %d to %d' % (i, frombase, tobase) if abstobase in [0, 1]: return '0' * ((i > 0) and int(i) or 0) elif abstobase > 256: return 'why?' # if execution gets here, we might want the result to be zfilled to a multiple of resize r = '' if tobase == 10: r = str(i) else: if toalphabet is None: if 62 <= abstobase <= 256: toalphabet = map(chr, range(abstobase)) else: toalphabet = (string.digits + string.letters)[:abstobase] if tobase < 0: i = -i j = 0 while i != 0: r = toalphabet[i % tobase] + r i /= tobase j += 1 if j >= too_big: raise Exception("set too_big bigger") if resize > 1: r = r.zfill(len(r) + resize - (len(r) % resize)) return r def main(*args): ''' ''' pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption('PyCopter') pygame.mouse.set_visible(0) bg = pygame.Surface(screen.get_size()).convert() bg.fill(BG_COLOR) font = pygame.font.SysFont(*FONT) distance_text = font.render('distance: 0', 1, TEXT_COLOR) distance_text_rect = distance_text.get_rect(topleft=DISTANCE_TEXT_POSITION) bg.blit(distance_text, distance_text_rect) best_text = font.render('best: 0', 1, TEXT_COLOR) best_text_rect = best_text.get_rect(topleft=BEST_TEXT_POSITION) bg.blit(best_text, best_text_rect) try: f = open(BEST_FILENAME) best_ever = int(rebase(f.read(), frombase=256, tobase=10)) f.close() del f except: f = open(BEST_FILENAME, 'w') f.write(rebase(0, tobase=256)) f.close() del f best_ever = 0 best_ever_text = font.render('best ever: ' + str(best_ever), 1, TEXT_COLOR) best_ever_text_rect = best_ever_text.get_rect(topleft=BEST_EVER_TEXT_POSITION) bg.blit(best_ever_text, best_ever_text_rect) how_to_text = multiline(pygame.font.SysFont(FONT[0], 20, True), how_to_play) how_to_text_rect = how_to_text.get_rect(center=(WIDTH / 2, HEIGHT / 2)) bg.blit(how_to_text, how_to_text_rect) shield_counter_font = pygame.font.SysFont(FONT[0], 36, True) screen.blit(bg, (0, 0)) pwnd_text = pygame.font.SysFont(FONT[0], 100, True).render('OWNED', 1, OWNED_TEXT_COLOR) pygame.display.flip() copter = Copter() copter_group = pygame.sprite.RenderPlain((copter, )) walls = pygame.sprite.RenderPlain((Wall(), )) smokes = pygame.sprite.RenderPlain() powerups = pygame.sprite.RenderPlain() missiles = pygame.sprite.RenderPlain() update_all = lambda: [copter_group.update(), walls.update(), smokes.update(), powerups.update(), missiles.update()] draw_all = lambda: [copter_group.draw(bg), walls.draw(bg), smokes.draw(bg), powerups.draw(bg), missiles.draw(bg)] clock = pygame.time.Clock() ceiling = Immobile(WIDTH, CEILING_FLOOR_HEIGHT) floor = Immobile(WIDTH, CEILING_FLOOR_HEIGHT, 0, HEIGHT - CEILING_FLOOR_HEIGHT) walls.add(floor) walls.add(ceiling) def remove_powerup(): copter.powerups -= 1 old_powerups = [x for x in powerups.sprites() if x.rect.top == POWERUP_POSITION[1]] old_powerups.sort(key=(lambda x: x.rect.left)) powerups.remove(old_powerups[-1]) # initialize state variables paused = False best = 0 force = 0 n_iterations = 1 shields_counter = -1 cave_height = CEILING_FLOOR_HEIGHT # linearly increasing thickness of ceiling CavePoints waiting = True while True: while waiting: clock.tick(FPS) events = pygame.event.get() event_types = [event.type for event in events] event_keys = [event.key for event in events if event.type == KEYDOWN] if FULLSCREEN_KEY in event_keys: pygame.display.toggle_fullscreen() if (QUIT in event_types) or (QUIT_KEY in event_keys): return 0 elif pygame.mouse.get_pressed()[0] or (ACTIVATION_KEY in event_keys): waiting = False bg.fill(BG_COLOR, how_to_text_rect) bg.fill(BG_COLOR, distance_text_rect) cave_height = CEILING_FLOOR_HEIGHT clock.tick(FPS) events = pygame.event.get() event_types = [event.type for event in events] event_keys = [event.key for event in events if event.type == KEYDOWN] if PAUSE_KEY in event_keys: paused = not paused if FULLSCREEN_KEY in event_keys: pygame.display.toggle_fullscreen() if (QUIT in event_types) or (QUIT_KEY in event_keys): return 0 if not paused: if copter.powerups > 0: if (MISSILE_KEY in event_keys) and not missiles.sprites(): remove_powerup() missiles.add(Missile(copter.rect.right, copter.rect.top)) elif (shields_counter < 0) and (SHIELD_KEY in event_keys): # draw a green box around copter, and stop checking for copter-wall collisions for SHIELD_TIME iterations remove_powerup() shields_counter = SHIELD_TIME copter.orig = copter.image, copter.rect copter.image, copter.rect = mk_image_rect(copter.rect.width + 2 * SHIELD_THICKNESS, copter.rect.height + 2 * SHIELD_THICKNESS, COPTER_X, copter.rect.top ) copter.image.fill(SHIELD_COLOR) copter.image.blit(copter.orig[0], (SHIELD_THICKNESS, SHIELD_THICKNESS)) if pygame.mouse.get_pressed()[0] or pygame.key.get_pressed()[ACTIVATION_KEY]: copter.dy += WIND else: copter.dy += GRAVITY if shields_counter < 0: pass elif shields_counter > 0: shields_counter -= 1 elif shields_counter == 0: y = copter.rect.top copter.image, copter.rect = copter.orig copter.rect.top = y bg.fill(BG_COLOR, shield_counter_text_rect) shields_counter = -1 # start checking for collisions again if shields_counter == SHIELD_WARN_TIME: copter.image.fill(SHIELD_WARN_COLOR) copter.image.blit(copter.orig[0], (SHIELD_THICKNESS, SHIELD_THICKNESS)) if (n_iterations % CAVE_TIME) == 0: h = int(random.normalvariate(cave_height + CAVE_STDDEV, CAVE_STDDEV)) if h < 0: h = CEILING_FLOOR_HEIGHT walls.add(CavePoint(h=h)) floory = h + HEIGHT - 2 * CEILING_FLOOR_HEIGHT - 2 * cave_height + CAVE_STDDEV if floory > HEIGHT: floory = HEIGHT - CEILING_FLOOR_HEIGHT walls.add(CavePoint(y=floory, h=(HEIGHT - floory))) if copter.can_squeeze_through(HEIGHT - 2 * (cave_height + 4 * CAVE_STDDEV)): # there's still plenty of room to squeeze through -- make it harder cave_height += CAVE_SPEED if (n_iterations % SMOKE_TIME) == 0: smokes.add(Smoke(copter.rect.centery)) if (n_iterations % WALL_TIME) == 0: walls.add(Wall()) if ((n_iterations % WALL_TIME) == WALL_TIME / 2) and (random.random() < POWERUP_FREQUENCY): powerups.add(PowerUp()) update_all() bg.fill(BG_COLOR, (0, 0, WIDTH, HEIGHT)) draw_all() distance_text = font.render('distance: %d' % copter.distance, 1, TEXT_COLOR) distance_text_rect = distance_text.get_rect(topleft=DISTANCE_TEXT_POSITION) distance_text_rect.width += 2 screen.blit(distance_text, distance_text_rect) screen.blit(best_text, best_text_rect) screen.blit(best_ever_text, best_ever_text_rect) pygame.display.flip() for powerup in pygame.sprite.spritecollide(copter, powerups, False): copter.powerups += 1 powerup.update = lambda: None # keep stored powerups from running off the screen powerup.rect.topleft = POWERUP_POSITION for i in range(1, copter.powerups): # separate multiple powerups powerup.rect.left += 20 pygame.sprite.groupcollide(missiles, walls, True, True) # remove colliding walls and missiles if (copter.rect.top > HEIGHT) or (copter.rect.bottom < 0): copter.rect.centery = HEIGHT / 2 copter.dy = 0 if shields_counter > 0: shield_counter_text = shield_counter_font.render(str(int(shields_counter / 10)), 1, SHIELD_COUNTER_COLOR) shield_counter_text_rect = shield_counter_text.get_rect(center=SHIELD_COUNTER_POSITION) bg.fill(WALL_COLOR, shield_counter_text_rect) bg.blit(shield_counter_text, shield_counter_text_rect) hit_walls = pygame.sprite.spritecollide(copter, walls, False) if hit_walls: if shields_counter > 0: # you have shields up -- bounce off floor and ceiling if max([wall.rect.bottom for wall in hit_walls]) > (HEIGHT - 5): copter.dy = 2 elif min([wall.rect.top for wall in hit_walls]) < 5: copter.dy = -2 else: # you died waiting = True bg.blit(pwnd_text, pwnd_text.get_rect(center=(WIDTH / 2, HEIGHT / 2))) walls.remove(*[wall for wall in walls.sprites() if wall not in [ceiling, floor]]) if copter.distance > best: best = copter.distance best_text = font.render('best: %d' % best, 1, TEXT_COLOR) best_text_rect = best_text.get_rect(topleft=BEST_TEXT_POSITION) bg.blit(best_text, best_text_rect) if best > best_ever: best_ever = best f = open(BEST_FILENAME, 'w') f.write(rebase(best_ever, tobase=256)) f.close() del f best_ever_text = font.render('best ever: ' + str(best_ever), 1, TEXT_COLOR) best_ever_text_rect = best_ever_text.get_rect(topleft=BEST_EVER_TEXT_POSITION) bg.blit(best_ever_text, best_ever_text_rect) copter.distance = copter.distance_ratio_counter = copter.dy = 0 copter.rect.centery = HEIGHT / 2 copter.powerups = 0 waiting = True screen.blit(bg, (0, 0)) # need to blit owned text before waiting OWNED_TIME pygame.display.flip() time.sleep(OWNED_TIME) for sprite_group in [smokes, powerups, missiles]: sprite_group.remove(*sprite_group.sprites()) for sprite_group in [powerups, walls, smokes, missiles]: # remove sprites if they wander off screen sprite_group.remove(*[sprite for sprite in sprite_group if (sprite.rect.right < 0) or (sprite.rect.left > WIDTH)]) screen.blit(bg, (0, 0)) n_iterations += 1 # end if not paused # end main event loop # end main function __version__ = 1 __email__ = 'faulkner@users.sf.net' __author__ = 'faulkner <' + __email__ + '>' # email me if you should need my full legal name __license__ = 'GNU GPL 2' __url__ = 'http://fauxlkner.sf.net' __todo__ = [ ] if __name__ == "__main__": sys.exit(main(*sys.argv[1:]))