- import os
- import random
- import pygame
- import hGame
- import hGame.config as cfg
- from hGame.image import load as iLoad
- import miscsprites
- import utils
- from hGame.memoizer import memoized
- # Import the android module. If we can't import it, set it to None - this lets
- # us test it, and check to see if we want android-specific behavior.
- try:
- import android
- except ImportError:
- android = None
- FPS = 60
- SCREEN_SIZE = (800, 480)
- # Messages
- INTRO_MSG = 'You are a blue block. Eat green and yellow. Avoid red.'
- LOSE_MSG = 'You ate %d block%s before succumbing to inevitable doom!'
- WITTICISMS = [
- 'Perhaps you should consider another career.',
- 'You are so special.',
- 'What an awe-inspiring score.',
- 'It was an honor to have known a block like you.',
- 'This is so much better than being productive, isn\'t it?',
- 'Isn\'t this game amazing?',
- 'Life\'s an empty white expanse, and then you die.',
- 'Achievement unlocked: waste time playing a cell phone game.',
- 'Hell isn\'t other people, it\'s giant smiling red squares.',
- 'Shoot for the moon. If you miss, you\'ll probably hit an asteroid or \
- something.',
- 'I\'m *extremely* impressed.'
- ]
- # --- Enemies
- HAPPY_ENEMY_PATH = os.path.join('images', 'happyenemy.png')
- SAD_ENEMY_PATH = os.path.join('images', 'sadenemy.png')
- START_ADD_CHANCE = 0.03
- CHANCE_CHANGE = 0.01
- ENEMIES_PER_LEVEL = 50
- # --- Player
- PLAYER_PATH = os.path.join('images', 'player.png')
- START_SCALE = 0.20
- # once this scale is reached, game will "zoom out" around the player to allow
- # infinite play
- MAX_SCALE = 0.80
- SCALE_CHANGE = (MAX_SCALE - START_SCALE) / ENEMIES_PER_LEVEL
- # --- Buttons
- LOSE_BUTTON_UP_PATH = os.path.join('images', 'losebuttonup.png')
- LOSE_BUTTON_DOWN_PATH = os.path.join('images', 'losebuttondown.png')
- START_BUTTON_UP_PATH = os.path.join('images', 'startbuttonup.png')
- START_BUTTON_DOWN_PATH = os.path.join('images', 'startbuttondown.png')
- # --- Fonts
- # android does not appear to have any system fonts; get_fonts() returns [None]
- DEFAULT_FONT = os.path.join('fonts', 'freesansbold.ttf')
- # --- Powerups
- POWERUP_PATH = os.path.join('images', 'powerup.png')
- # chance each frame of adding a powerup
- POWERUP_CHANCE = 0.003
- ALL_POWERUP_NAMES = ['shrink']
- # --- Misc
- # the ending that will be passed to utils.getPathWithEnd() after loss
- LOSE_PATH_ENDING = 'faded'
- # chance each frame of adding an enemy
- addChance = None
- sprites = pygame.sprite.LayeredUpdates()
- enemies = pygame.sprite.Group()
- powerups = pygame.sprite.Group()
- powerupButtons = pygame.sprite.Group()
- player = None
- blocks = 0
- def renderPowerupAlertText(name):
- return hGame.font.render(DEFAULT_FONT, 20, name.title() + '!')
- def addEnemy(player):
- """
- Generate and add enemy to required groups based on player size.
- Also return new enemy.
- """
- scale = player.scale + (SCALE_CHANGE * random.triangular(-6, 9, 0))
- path = HAPPY_ENEMY_PATH if scale > player.scale else SAD_ENEMY_PATH
- sprite = miscsprites.OneDirectional(
- path=path,
- direction=random.choice(['left', 'right', 'up', 'down']),
- maxSpeed=random.randint(20, 250),
- scale=scale,
- groups=(sprites, enemies)
- )
- return sprite
- def addPowerup(image=None):
- """
- Generate and add powerup to sprites.
- Optional image will be blitted on masterImage of powerup and centered, so
- powerups can have different text/images on them. Default None.
- Also return new powerup.
- """
- result = miscsprites.OneDirectional(
- path=POWERUP_PATH,
- direction=random.choice(['left', 'right', 'up', 'down']),
- maxSpeed=random.randint(200, 300),
- groups=(sprites, powerups)
- )
- if image:
- rect = image.get_rect(center=result.masterImage.get_rect().center)
- result.masterImage.blit(image, rect)
- # So that __setattr__() will be called - looks damn silly though. It's
- # a shame that python has no way to call a method whenever an attribute
- # changes, not just when it's assigned to.
- result.masterImage = result.masterImage
- return result
- @memoized
- def _getFadingSurfs(surf, totalDuration, duration):
- alphaChg = -int(255 / (totalDuration / duration))
- return [hGame.utils.setPixelAlpha(surf, lambda a: min(alpha, a)) \
- for alpha in xrange(255, 0, alphaChg)]
- def addFader(surf, totalDuration=0.25, duration=0.05):
- """
- Generate and add Animation to sprites, then return it.
- Animation is composed of Surfaces gradually fading in per-pixel alpha from
- 255 to 0.
- duration is number of seconds between each change.
- """
- return hGame.sprite.Animation(
- images=_getFadingSurfs(surf, totalDuration, duration),
- layer=1,
- groups=(sprites,),
- loop=False,
- duration=duration
- )
- def refreshEnemies():
- """Make sure enemies larger than player are happy, and others sad."""
- for enemy in enemies:
- if enemy.scale > player.scale and enemy.path == SAD_ENEMY_PATH:
- enemy.path = HAPPY_ENEMY_PATH
- elif enemy.scale <= player.scale and enemy.path == HAPPY_ENEMY_PATH:
- enemy.path = SAD_ENEMY_PATH
- def resetGame():
- """
- Remove all sprites but player.
- Reset player size, blocks eaten, enemy generation chance, and powerups.
- """
- global player
- global blocks
- global addChance
- sprites.empty()
- enemies.empty()
- powerups.empty()
- player = hGame.sprite.PathSprite(
- path=PLAYER_PATH,
- groups=(sprites,),
- scale=START_SCALE
- )
- hGame.utils.movePercent(player.rect)
- blocks = 0
- addChance = START_ADD_CHANCE
- def handleIntro():
- """
- Show a simple intro screen.
- Return True if user clicked "Play", False if they quit.
- """
- text = hGame.font.Text(
- text=INTRO_MSG,
- path=DEFAULT_FONT,
- size=25,
- groups=(sprites,)
- )
- hGame.utils.movePercent(text.rect, y=0.3)
- startButton = hGame.button.Button(
- mainImage=iLoad(START_BUTTON_UP_PATH),
- downImage=iLoad(START_BUTTON_DOWN_PATH),
- groups=(sprites,)
- )
- hGame.utils.movePercent(startButton.rect)
- while True:
- cfg.clock.tick(FPS)
- cfg.screen.fill((255, 255, 255))
- if android:
- if android.check_pause():
- android.wait_for_resume()
- for evt in pygame.event.get():
- if evt.type == pygame.KEYDOWN and evt.key == pygame.K_ESCAPE:
- return False
- startButton.handleEvent(evt)
- if startButton.mouseClicked:
- return True
- sprites.update()
- sprites.draw(cfg.screen)
- pygame.display.flip()
- def handleGame():
- """
- Reset sprites, then run the main game until user loses or quits.
- Return True if user died, False if user quit.
- Does not kill or modify existing sprites at all when finished.
- """
- global blocks
- global addChance
- resetGame()
- # fill and update screen with white first because LayeredUpdates only
- # updates changed areas
- cfg.screen.fill((255, 255, 255))
- pygame.display.flip()
- levelText = hGame.font.Text(
- text='',
- size=20,
- path=DEFAULT_FONT,
- groups=(sprites,),
- layer=1
- )
- powerupText = hGame.font.Text(
- text='',
- size=15,
- path=DEFAULT_FONT,
- groups=(sprites,),
- layer=1
- )
- def updateLevelText():
- """
- Set levelText based on blocks eaten, also move it and reset background
- to None.
- """
- levelText.text = '%d/%d' % \
- (blocks, (blocks / ENEMIES_PER_LEVEL + 1) * ENEMIES_PER_LEVEL)
- levelText.rect.topleft = (5, 5)
- levelText.background = None
- updateLevelText()
- while True:
- cfg.clock.tick(FPS)
- cfg.screen.fill((255, 255, 255))
- if android:
- if android.check_pause():
- android.wait_for_resume()
- if random.random() < addChance:
- addEnemy(player)
- if random.random() < POWERUP_CHANCE:
- name = random.choice(ALL_POWERUP_NAMES)
- addPowerup()
- collided = pygame.sprite.spritecollide(player, sprites, False)
- for sprite in collided:
- if sprite in enemies:
- if sprite.scale <= player.scale:
- player.scale += SCALE_CHANGE
- sprite.kill()
- blocks += 1
- if player.scale >= MAX_SCALE:
- utils.zoom(player, enemies, START_SCALE / MAX_SCALE)
- addChance += CHANCE_CHANGE
- updateLevelText()
- refreshEnemies()
- else:
- return True
- elif sprite in powerups:
- # it's a powerup
- name = random.choice(ALL_POWERUP_NAMES)
- if name == 'shrink':
- for enemy in enemies:
- if enemy.scale > player.scale:
- enemy.scale = player.scale
- refreshEnemies()
- sprite.kill()
- fadingText = addFader(renderPowerupAlertText(name))
- fadingText.rect.centerx = sprite.rect.centerx
- # put it above the player if possible
- fadingText.rect.centery = max(
- sprite.rect.y - 15,
- fadingText.rect.h / 2
- )
- for evt in pygame.event.get():
- if evt.type == pygame.MOUSEBUTTONDOWN \
- or evt.type == pygame.MOUSEMOTION and 1 in evt.buttons:
- player.rect.center = evt.pos
- elif evt.type == pygame.KEYDOWN:
- if evt.key == pygame.K_ESCAPE:
- return False
- if (blocks + 1) % ENEMIES_PER_LEVEL == 0 and not cfg.clock.frame % 5:
- if levelText.background == None:
- levelText.background = (255, 0, 0)
- else:
- levelText.background = None
- sprites.update()
- changed = sprites.draw(cfg.screen)
- pygame.display.update(changed)
- def handleLose():
- """
- Show the lose screen until user chooses to play again or quit.
- Return True if user wants to play again, False if user quit.
- """
- player.kill()
- for sprite in sprites:
- try:
- sprite.path = utils.getPathWithEnd(sprite.path, LOSE_PATH_ENDING)
- except IOError:
- pass
- text = hGame.font.Text(
- path=DEFAULT_FONT,
- size=25,
- text=LOSE_MSG % (blocks, '' if blocks == 1 else 's'),
- groups=(sprites,),
- layer=1
- )
- hGame.utils.movePercent(text.rect, y=0.2)
- text = hGame.font.Text(
- path=DEFAULT_FONT,
- size=20,
- text=random.choice(WITTICISMS),
- groups=(sprites,),
- layer=1
- )
- hGame.utils.movePercent(text.rect, y=0.4)
- loseButton = hGame.button.Button(
- mainImage=iLoad(LOSE_BUTTON_UP_PATH),
- downImage=iLoad(LOSE_BUTTON_DOWN_PATH),
- groups=(sprites,),
- layer=1
- )
- hGame.utils.movePercent(loseButton.rect, y=0.7)
- while True:
- cfg.clock.tick(FPS)
- cfg.screen.fill((255, 255, 255))
- if android:
- if android.check_pause():
- android.wait_for_resume()
- if random.random() < addChance:
- enemy = addEnemy(player)
- enemy.path = utils.getPathWithEnd(enemy.path, LOSE_PATH_ENDING)
- if random.random() < POWERUP_CHANCE:
- powerup = addPowerup()
- powerup.path = utils.getPathWithEnd(powerup.path, LOSE_PATH_ENDING)
- for evt in pygame.event.get():
- if evt.type == pygame.KEYDOWN and evt.key == pygame.K_ESCAPE:
- return False
- loseButton.handleEvent(evt)
- if loseButton.mouseClicked:
- return True
- sprites.update()
- sprites.draw(cfg.screen)
- pygame.display.flip()
- def main():
- pygame.init()
- cfg.screen = pygame.display.set_mode(SCREEN_SIZE)
- # map back button to escape key
- if android:
- android.map_key(android.KEYCODE_BACK, pygame.K_ESCAPE)
- cfg.screen.fill((255, 255, 255))
- utils.printCenteredText(DEFAULT_FONT, 'Loading text effects...')
- # Cache all fading powerup text, which is too slow to generate in game due
- # to usage of per-pixel alphas instead of surface alphas. Takes ~0.5s per
- # powerup on my Droid X :(
- for name in ALL_POWERUP_NAMES:
- renderPowerupAlertText(name)
- if handleIntro():
- while True:
- if not handleGame():
- break
- if not handleLose():
- break
- # not run on android
- if __name__ == '__main__':
- main()
e
Posted by Anonymous on Mon 31st Jan 2011 20:27
raw | new post
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.