pastebin - collaborative debugging tool
fferen.kpaste.net RSS


e
Posted by Anonymous on Mon 31st Jan 2011 20:27
raw | new post

  1. import os
  2. import random
  3.  
  4. import pygame
  5.  
  6. import hGame
  7. import hGame.config as cfg
  8. from hGame.image import load as iLoad
  9.  
  10. import miscsprites
  11. import utils
  12. from hGame.memoizer import memoized
  13.  
  14. # Import the android module. If we can't import it, set it to None - this lets
  15. # us test it, and check to see if we want android-specific behavior.
  16. try:
  17.     import android
  18. except ImportError:
  19.     android = None
  20.  
  21. FPS = 60
  22.  
  23. SCREEN_SIZE = (800, 480)
  24.  
  25. # Messages
  26. INTRO_MSG = 'You are a blue block. Eat green and yellow. Avoid red.'
  27.  
  28. LOSE_MSG = 'You ate %d block%s before succumbing to inevitable doom!'
  29.  
  30. WITTICISMS = [
  31.         'Perhaps you should consider another career.',
  32.         'You are so special.',
  33.         'What an awe-inspiring score.',
  34.         'It was an honor to have known a block like you.',
  35.         'This is so much better than being productive, isn\'t it?',
  36.         'Isn\'t this game amazing?',
  37.         'Life\'s an empty white expanse, and then you die.',
  38.         'Achievement unlocked: waste time playing a cell phone game.',
  39.         'Hell isn\'t other people, it\'s giant smiling red squares.',
  40.         'Shoot for the moon. If you miss, you\'ll probably hit an asteroid or \
  41. something.',
  42.         'I\'m *extremely* impressed.'
  43.         ]
  44.  
  45. # --- Enemies
  46. HAPPY_ENEMY_PATH = os.path.join('images', 'happyenemy.png')
  47. SAD_ENEMY_PATH = os.path.join('images', 'sadenemy.png')
  48.  
  49. START_ADD_CHANCE = 0.03
  50. CHANCE_CHANGE = 0.01
  51.  
  52. ENEMIES_PER_LEVEL = 50
  53.  
  54. # --- Player
  55. PLAYER_PATH = os.path.join('images', 'player.png')
  56.  
  57. START_SCALE = 0.20
  58. # once this scale is reached, game will "zoom out" around the player to allow
  59. # infinite play
  60. MAX_SCALE = 0.80
  61. SCALE_CHANGE = (MAX_SCALE - START_SCALE) / ENEMIES_PER_LEVEL
  62.  
  63. # --- Buttons
  64. LOSE_BUTTON_UP_PATH = os.path.join('images', 'losebuttonup.png')
  65. LOSE_BUTTON_DOWN_PATH = os.path.join('images', 'losebuttondown.png')
  66.  
  67. START_BUTTON_UP_PATH = os.path.join('images', 'startbuttonup.png')
  68. START_BUTTON_DOWN_PATH = os.path.join('images', 'startbuttondown.png')
  69.  
  70. # --- Fonts
  71. # android does not appear to have any system fonts; get_fonts() returns [None]
  72. DEFAULT_FONT = os.path.join('fonts', 'freesansbold.ttf')
  73.  
  74. # --- Powerups
  75. POWERUP_PATH = os.path.join('images', 'powerup.png')
  76.  
  77. # chance each frame of adding a powerup
  78. POWERUP_CHANCE = 0.003
  79. ALL_POWERUP_NAMES = ['shrink']
  80.  
  81. # --- Misc
  82. # the ending that will be passed to utils.getPathWithEnd() after loss
  83. LOSE_PATH_ENDING = 'faded'
  84.  
  85. # chance each frame of adding an enemy
  86. addChance = None
  87.  
  88. sprites = pygame.sprite.LayeredUpdates()
  89.  
  90. enemies = pygame.sprite.Group()
  91.  
  92. powerups = pygame.sprite.Group()
  93.  
  94. powerupButtons = pygame.sprite.Group()
  95.  
  96. player = None
  97.  
  98. blocks = 0
  99.  
  100. def renderPowerupAlertText(name):
  101.     return hGame.font.render(DEFAULT_FONT, 20, name.title() + '!')
  102.  
  103. def addEnemy(player):
  104.     """
  105.     Generate and add enemy to required groups based on player size.
  106.  
  107.     Also return new enemy.
  108.     """
  109.     scale = player.scale + (SCALE_CHANGE * random.triangular(-6, 9, 0))
  110.     path = HAPPY_ENEMY_PATH if scale > player.scale else SAD_ENEMY_PATH
  111.     sprite = miscsprites.OneDirectional(
  112.             path=path,
  113.             direction=random.choice(['left', 'right', 'up', 'down']),
  114.             maxSpeed=random.randint(20, 250),
  115.             scale=scale,
  116.             groups=(sprites, enemies)
  117.             )
  118.     return sprite
  119.  
  120. def addPowerup(image=None):
  121.     """
  122.     Generate and add powerup to sprites.
  123.  
  124.     Optional image will be blitted on masterImage of powerup and centered, so
  125.     powerups can have different text/images on them. Default None.
  126.  
  127.     Also return new powerup.
  128.     """
  129.     result = miscsprites.OneDirectional(
  130.             path=POWERUP_PATH,
  131.             direction=random.choice(['left', 'right', 'up', 'down']),
  132.             maxSpeed=random.randint(200, 300),
  133.             groups=(sprites, powerups)
  134.             )
  135.     if image:
  136.         rect = image.get_rect(center=result.masterImage.get_rect().center)
  137.         result.masterImage.blit(image, rect)
  138.         # So that __setattr__() will be called - looks damn silly though. It's
  139.         # a shame that python has no way to call a method whenever an attribute
  140.         # changes, not just when it's assigned to.
  141.         result.masterImage = result.masterImage
  142.     return result
  143.  
  144. @memoized
  145. def _getFadingSurfs(surf, totalDuration, duration):
  146.     alphaChg = -int(255 / (totalDuration / duration))
  147.     return [hGame.utils.setPixelAlpha(surf, lambda a: min(alpha, a)) \
  148.                     for alpha in xrange(255, 0, alphaChg)]
  149.  
  150. def addFader(surf, totalDuration=0.25, duration=0.05):
  151.     """
  152.     Generate and add Animation to sprites, then return it.
  153.     Animation is composed of Surfaces gradually fading in per-pixel alpha from
  154.     255 to 0.
  155.  
  156.     duration is number of seconds between each change.
  157.     """
  158.     return hGame.sprite.Animation(
  159.             images=_getFadingSurfs(surf, totalDuration, duration),
  160.             layer=1,
  161.             groups=(sprites,),
  162.             loop=False,
  163.             duration=duration
  164.             )
  165.  
  166. def refreshEnemies():
  167.     """Make sure enemies larger than player are happy, and others sad."""
  168.     for enemy in enemies:
  169.         if enemy.scale > player.scale and enemy.path == SAD_ENEMY_PATH:
  170.             enemy.path = HAPPY_ENEMY_PATH
  171.         elif enemy.scale <= player.scale and enemy.path == HAPPY_ENEMY_PATH:
  172.             enemy.path = SAD_ENEMY_PATH
  173.  
  174. def resetGame():
  175.     """
  176.     Remove all sprites but player.
  177.     Reset player size, blocks eaten, enemy generation chance, and powerups.
  178.     """
  179.     global player
  180.     global blocks
  181.     global addChance
  182.  
  183.     sprites.empty()
  184.     enemies.empty()
  185.     powerups.empty()
  186.  
  187.     player = hGame.sprite.PathSprite(
  188.             path=PLAYER_PATH,
  189.             groups=(sprites,),
  190.             scale=START_SCALE
  191.             )
  192.     hGame.utils.movePercent(player.rect)
  193.  
  194.     blocks = 0
  195.     addChance = START_ADD_CHANCE
  196.  
  197. def handleIntro():
  198.     """
  199.     Show a simple intro screen.
  200.  
  201.     Return True if user clicked "Play", False if they quit.
  202.     """
  203.     text = hGame.font.Text(
  204.             text=INTRO_MSG,
  205.             path=DEFAULT_FONT,
  206.             size=25,
  207.             groups=(sprites,)
  208.             )
  209.     hGame.utils.movePercent(text.rect, y=0.3)
  210.  
  211.     startButton = hGame.button.Button(
  212.             mainImage=iLoad(START_BUTTON_UP_PATH),
  213.             downImage=iLoad(START_BUTTON_DOWN_PATH),
  214.             groups=(sprites,)
  215.             )
  216.     hGame.utils.movePercent(startButton.rect)
  217.     while True:
  218.         cfg.clock.tick(FPS)
  219.         cfg.screen.fill((255, 255, 255))
  220.         if android:
  221.             if android.check_pause():
  222.                 android.wait_for_resume()
  223.         for evt in pygame.event.get():
  224.             if evt.type == pygame.KEYDOWN and evt.key == pygame.K_ESCAPE:
  225.                 return False
  226.             startButton.handleEvent(evt)
  227.             if startButton.mouseClicked:
  228.                 return True
  229.         sprites.update()
  230.         sprites.draw(cfg.screen)
  231.         pygame.display.flip()
  232.  
  233. def handleGame():
  234.     """
  235.     Reset sprites, then run the main game until user loses or quits.
  236.  
  237.     Return True if user died, False if user quit.
  238.    
  239.     Does not kill or modify existing sprites at all when finished.
  240.     """
  241.     global blocks
  242.     global addChance
  243.  
  244.     resetGame()
  245.  
  246.     # fill and update screen with white first because LayeredUpdates only
  247.     # updates changed areas
  248.     cfg.screen.fill((255, 255, 255))
  249.     pygame.display.flip()
  250.  
  251.     levelText = hGame.font.Text(
  252.             text='',
  253.             size=20,
  254.             path=DEFAULT_FONT,
  255.             groups=(sprites,),
  256.             layer=1
  257.             )
  258.  
  259.     powerupText = hGame.font.Text(
  260.             text='',
  261.             size=15,
  262.             path=DEFAULT_FONT,
  263.             groups=(sprites,),
  264.             layer=1
  265.             )
  266.  
  267.     def updateLevelText():
  268.         """
  269.         Set levelText based on blocks eaten, also move it and reset background
  270.         to None.
  271.         """
  272.         levelText.text = '%d/%d' % \
  273.                 (blocks, (blocks / ENEMIES_PER_LEVEL + 1) * ENEMIES_PER_LEVEL)
  274.         levelText.rect.topleft = (5, 5)
  275.         levelText.background = None
  276.  
  277.     updateLevelText()
  278.  
  279.     while True:
  280.         cfg.clock.tick(FPS)
  281.         cfg.screen.fill((255, 255, 255))
  282.         if android:
  283.             if android.check_pause():
  284.                 android.wait_for_resume()
  285.         if random.random() < addChance:
  286.             addEnemy(player)
  287.         if random.random() < POWERUP_CHANCE:
  288.             name = random.choice(ALL_POWERUP_NAMES)
  289.             addPowerup()
  290.         collided = pygame.sprite.spritecollide(player, sprites, False)
  291.         for sprite in collided:
  292.             if sprite in enemies:
  293.                 if sprite.scale <= player.scale:
  294.                     player.scale += SCALE_CHANGE
  295.                     sprite.kill()
  296.                     blocks += 1
  297.                     if player.scale >= MAX_SCALE:
  298.                         utils.zoom(player, enemies, START_SCALE / MAX_SCALE)
  299.                         addChance += CHANCE_CHANGE
  300.                     updateLevelText()
  301.                     refreshEnemies()
  302.                 else:
  303.                     return True
  304.             elif sprite in powerups:
  305.                 # it's a powerup
  306.                 name = random.choice(ALL_POWERUP_NAMES)
  307.                 if name == 'shrink':
  308.                     for enemy in enemies:
  309.                         if enemy.scale > player.scale:
  310.                             enemy.scale = player.scale
  311.                     refreshEnemies()
  312.                 sprite.kill()
  313.                 fadingText = addFader(renderPowerupAlertText(name))
  314.                 fadingText.rect.centerx = sprite.rect.centerx
  315.                 # put it above the player if possible
  316.                 fadingText.rect.centery = max(
  317.                         sprite.rect.y - 15,
  318.                         fadingText.rect.h / 2
  319.                         )
  320.         for evt in pygame.event.get():
  321.             if evt.type == pygame.MOUSEBUTTONDOWN \
  322.                     or evt.type == pygame.MOUSEMOTION and 1 in evt.buttons:
  323.                 player.rect.center = evt.pos
  324.             elif evt.type == pygame.KEYDOWN:
  325.                 if evt.key == pygame.K_ESCAPE:
  326.                     return False
  327.         if (blocks + 1) % ENEMIES_PER_LEVEL == 0 and not cfg.clock.frame % 5:
  328.             if levelText.background == None:
  329.                 levelText.background = (255, 0, 0)
  330.             else:
  331.                 levelText.background = None
  332.         sprites.update()
  333.         changed = sprites.draw(cfg.screen)
  334.         pygame.display.update(changed)
  335.  
  336. def handleLose():
  337.     """
  338.     Show the lose screen until user chooses to play again or quit.
  339.  
  340.     Return True if user wants to play again, False if user quit.
  341.     """
  342.     player.kill()
  343.  
  344.     for sprite in sprites:
  345.         try:
  346.             sprite.path = utils.getPathWithEnd(sprite.path, LOSE_PATH_ENDING)
  347.         except IOError:
  348.             pass
  349.  
  350.     text = hGame.font.Text(
  351.             path=DEFAULT_FONT,
  352.             size=25,
  353.             text=LOSE_MSG % (blocks, '' if blocks == 1 else 's'),
  354.             groups=(sprites,),
  355.             layer=1
  356.             )
  357.     hGame.utils.movePercent(text.rect, y=0.2)
  358.  
  359.     text = hGame.font.Text(
  360.             path=DEFAULT_FONT,
  361.             size=20,
  362.             text=random.choice(WITTICISMS),
  363.             groups=(sprites,),
  364.             layer=1
  365.             )
  366.     hGame.utils.movePercent(text.rect, y=0.4)
  367.  
  368.     loseButton = hGame.button.Button(
  369.             mainImage=iLoad(LOSE_BUTTON_UP_PATH),
  370.             downImage=iLoad(LOSE_BUTTON_DOWN_PATH),
  371.             groups=(sprites,),
  372.             layer=1
  373.             )
  374.     hGame.utils.movePercent(loseButton.rect, y=0.7)
  375.  
  376.     while True:
  377.         cfg.clock.tick(FPS)
  378.         cfg.screen.fill((255, 255, 255))
  379.         if android:
  380.             if android.check_pause():
  381.                 android.wait_for_resume()
  382.         if random.random() < addChance:
  383.             enemy = addEnemy(player)
  384.             enemy.path = utils.getPathWithEnd(enemy.path, LOSE_PATH_ENDING)
  385.         if random.random() < POWERUP_CHANCE:
  386.             powerup = addPowerup()
  387.             powerup.path = utils.getPathWithEnd(powerup.path, LOSE_PATH_ENDING)
  388.         for evt in pygame.event.get():
  389.             if evt.type == pygame.KEYDOWN and evt.key == pygame.K_ESCAPE:
  390.                 return False
  391.             loseButton.handleEvent(evt)
  392.             if loseButton.mouseClicked:
  393.                 return True
  394.         sprites.update()
  395.         sprites.draw(cfg.screen)
  396.         pygame.display.flip()
  397.  
  398. def main():
  399.     pygame.init()
  400.  
  401.     cfg.screen = pygame.display.set_mode(SCREEN_SIZE)
  402.  
  403.     # map back button to escape key
  404.     if android:
  405.         android.map_key(android.KEYCODE_BACK, pygame.K_ESCAPE)
  406.  
  407.     cfg.screen.fill((255, 255, 255))
  408.     utils.printCenteredText(DEFAULT_FONT, 'Loading text effects...')
  409.     # Cache all fading powerup text, which is too slow to generate in game due
  410.     # to usage of per-pixel alphas instead of surface alphas. Takes ~0.5s per
  411.     # powerup on my Droid X :(
  412.     for name in ALL_POWERUP_NAMES:
  413.         renderPowerupAlertText(name)
  414.  
  415.     if handleIntro():
  416.         while True:
  417.             if not handleGame():
  418.                 break
  419.             if not handleLose():
  420.                 break
  421.  
  422. # not run on android
  423. if __name__ == '__main__':
  424.     main()

Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at