listing29-4.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import os, sys, pygame
  2. from pygame.locals import *
  3. import objects, config
  4. "This module contains the main game logic of the Squish game."
  5. class State:
  6. """
  7. A generic game state class that can handle events and display
  8. itself on a given surface.
  9. """
  10. def handle(self, event):
  11. """
  12. Default event handling only deals with quitting.
  13. """
  14. if event.type == QUIT:
  15. sys.exit()
  16. if event.type == KEYDOWN and event.key == K_ESCAPE:
  17. sys.exit()
  18. def first_display(self, screen):
  19. """
  20. Used to display the State for the first time. Fills the screen
  21. with the background color.
  22. """
  23. screen.fill(config.background_color)
  24. # Remember to call flip, to make the changes visible:
  25. pygame.display.flip()
  26. def display(self, screen):
  27. """
  28. Used to display the State after it has already been displayed
  29. once. The default behavior is to do nothing.
  30. """
  31. pass
  32. class Level(State):
  33. """
  34. A game level. Takes care of counting how many weights have been
  35. dropped, moving the sprites around, and other tasks relating to
  36. game logic.
  37. """
  38. def __init__(self, number=1):
  39. self.number = number
  40. # How many weights remain to dodge in this level?
  41. self.remaining = config.weights_per_level
  42. speed = config.drop_speed
  43. # One speed_increase added for each level above 1:
  44. speed += (self.number-1) * config.speed_increase
  45. # Create the weight and banana:
  46. self.weight = objects.Weight(speed)
  47. self.banana = objects.Banana()
  48. both = self.weight, self.banana # This could contain more sprites...
  49. self.sprites = pygame.sprite.RenderUpdates(both)
  50. def update(self, game):
  51. "Updates the game state from the previous frame."
  52. # Update all sprites:
  53. self.sprites.update()
  54. # If the banana touches the weight, tell the game to switch to
  55. # a GameOver state:
  56. if self.banana.touches(self.weight):
  57. game.next_state = GameOver()
  58. # Otherwise, if the weight has landed, reset it. If all the
  59. # weights of this level have been dodged, tell the game to
  60. # switch to a LevelCleared state:
  61. elif self.weight.landed:
  62. self.weight.reset()
  63. self.remaining -= 1
  64. if self.remaining == 0:
  65. game.next_state = LevelCleared(self.number)
  66. def display(self, screen):
  67. """
  68. Displays the state after the first display (which simply wipes
  69. the screen). As opposed to firstDisplay, this method uses
  70. pygame.display.update with a list of rectangles that need to
  71. be updated, supplied from self.sprites.draw.
  72. """
  73. screen.fill(config.background_color)
  74. updates = self.sprites.draw(screen)
  75. pygame.display.update(updates)
  76. class Paused(State):
  77. """
  78. A simple, paused game state, which may be broken out of by pressing
  79. either a keyboard key or the mouse button.
  80. """
  81. finished = 0 # Has the user ended the pause?
  82. image = None # Set this to a file name if you want an image
  83. text = '' # Set this to some informative text
  84. def handle(self, event):
  85. """
  86. Handles events by delegating to State (which handles quitting
  87. in general) and by reacting to key presses and mouse
  88. clicks. If a key is pressed or the mouse is clicked,
  89. self.finished is set to true.
  90. """
  91. State.handle(self, event)
  92. if event.type in [MOUSEBUTTONDOWN, KEYDOWN]:
  93. self.finished = 1
  94. def update(self, game):
  95. """
  96. Update the level. If a key has been pressed or the mouse has
  97. been clicked (i.e., self.finished is true), tell the game to
  98. move to the state represented by self.next_state() (should be
  99. implemented by subclasses).
  100. """
  101. if self.finished:
  102. game.next_state = self.next_state()
  103. def first_display(self, screen):
  104. """
  105. The first time the Paused state is displayed, draw the image
  106. (if any) and render the text.
  107. """
  108. # First, clear the screen by filling it with the background color:
  109. screen.fill(config.background_color)
  110. # Create a Font object with the default appearance, and specified size:
  111. font = pygame.font.Font(None, config.font_size)
  112. # Get the lines of text in self.text, ignoring empty lines at
  113. # the top or bottom:
  114. lines = self.text.strip().splitlines()
  115. # Calculate the height of the text (using font.get_linesize()
  116. # to get the height of each line of text):
  117. height = len(lines) * font.get_linesize()
  118. # Calculate the placement of the text (centered on the screen):
  119. center, top = screen.get_rect().center
  120. top -= height // 2
  121. # If there is an image to display...
  122. if self.image:
  123. # load it:
  124. image = pygame.image.load(self.image).convert()
  125. # get its rect:
  126. r = image.get_rect()
  127. # move the text down by half the image height:
  128. top += r.height // 2
  129. # place the image 20 pixels above the text:
  130. r.midbottom = center, top - 20
  131. # blit the image to the screen:
  132. screen.blit(image, r)
  133. antialias = 1 # Smooth the text
  134. black = 0, 0, 0 # Render it as black
  135. # Render all the lines, starting at the calculated top, and
  136. # move down font.get_linesize() pixels for each line:
  137. for line in lines:
  138. text = font.render(line.strip(), antialias, black)
  139. r = text.get_rect()
  140. r.midtop = center, top
  141. screen.blit(text, r)
  142. top += font.get_linesize()
  143. # Display all the changes:
  144. pygame.display.flip()
  145. class Info(Paused):
  146. """
  147. A simple paused state that displays some information about the
  148. game. It is followed by a Level state (the first level).
  149. """
  150. next_state = Level
  151. text = '''
  152. In this game you are a banana,
  153. trying to survive a course in
  154. self-defense against fruit, where the
  155. participants will "defend" themselves
  156. against you with a 16 ton weight.'''
  157. class StartUp(Paused):
  158. """
  159. A paused state that displays a splash image and a welcome
  160. message. It is followed by an Info state.
  161. """
  162. next_state = Info
  163. image = config.splash_image
  164. text = '''
  165. Welcome to Squish,
  166. the game of Fruit Self-Defense'''
  167. class LevelCleared(Paused):
  168. """
  169. A paused state that informs the user that he or she has cleared a
  170. given level. It is followed by the next level state.
  171. """
  172. def __init__(self, number):
  173. self.number = number
  174. self.text = '''Level {} cleared
  175. Click to start next level'''.format(self.number)
  176. def next_state(self):
  177. return Level(self.number + 1)
  178. class GameOver(Paused):
  179. """
  180. A state that informs the user that he or she has lost the
  181. game. It is followed by the first level.
  182. """
  183. next_state = Level
  184. text = '''
  185. Game Over
  186. Click to Restart, Esc to Quit'''
  187. class Game:
  188. """
  189. A game object that takes care of the main event loop, including
  190. changing between the different game states.
  191. """
  192. def __init__(self, *args):
  193. # Get the directory where the game and the images are located:
  194. path = os.path.abspath(args[0])
  195. dir = os.path.split(path)[0]
  196. # Move to that directory (so that the image files may be
  197. # opened later on):
  198. os.chdir(dir)
  199. # Start with no state:
  200. self.state = None
  201. # Move to StartUp in the first event loop iteration:
  202. self.next_state = StartUp()
  203. def run(self):
  204. """
  205. This method sets things in motion. It performs some vital
  206. initialization tasks, and enters the main event loop.
  207. """
  208. pygame.init() # This is needed to initialize all the pygame modules
  209. # Decide whether to display the game in a window or to use the
  210. # full screen:
  211. flag = 0 # Default (window) mode
  212. if config.full_screen:
  213. flag = FULLSCREEN # Full screen mode
  214. screen_size = config.screen_size
  215. screen = pygame.display.set_mode(screen_size, flag)
  216. pygame.display.set_caption('Fruit Self Defense')
  217. pygame.mouse.set_visible(False)
  218. # The main loop:
  219. while True:
  220. # (1) If nextState has been changed, move to the new state, and
  221. # display it (for the first time):
  222. if self.state != self.next_state:
  223. self.state = self.next_state
  224. self.state.first_display(screen)
  225. # (2) Delegate the event handling to the current state:
  226. for event in pygame.event.get():
  227. self.state.handle(event)
  228. # (3) Update the current state:
  229. self.state.update(self)
  230. # (4) Display the current state:
  231. self.state.display(screen)
  232. if __name__ == '__main__':
  233. game = Game(*sys.argv)
  234. game.run()