#! /usr/bin/python
import sys, pygame


# =====================================================================
# GLOBAL VARIABLES
#    - list all variables that need to be globally accessible

# define the size of the pygame display window
scrSize = scrWidth, scrHeight = 640,480
gameScreen = None   # the main display screen
backImage = None    # the loaded background image
scrRefreshRate = 40 # the pause (in milliseconds) between updates
keepPlaying = True  # flag to identify if the game should continue

# =====================================================================
# SETUP ROUTINE    
#    - initializes the pygame display screen and background image

def gameSetup():

   # specify the global variables the setup routine needs to access
   global gameScreen, scrSize, backImage

   # initialize pygame
   pygame.init()

   # initialize the display screen
   gameScreen = pygame.display.set_mode(scrSize)

   # load and display the background image
   backImage = pygame.image.load('backdrop.gif')
   gameScreen.blit(backImage, (0,0))


# =====================================================================
# GAME OBJECT
#     controls the basic movable in-game objects (ships in this case)
#    
# Each game object has several properties:
#
#     origImage: the loaded image to represent that object
#     image: the current display image for the object
#             (rotated and zoomed appropriately from the original)
#     position = x,y: the x,y coordinates of the object
#     facingDir: the direction the object is currently facing
#     velocity: the horizontal/vertical distance covered
#                by the object each game step
#     scale: the scaling (zoom) factor currently used for the object
#     selected: a true/false value indicating if the object is
#                currently selected
#
# Each game object also has several actions that can be applied to it:
#
#      __init__ : the initialization routine for the object
#      update: the routine applied each step to update the
#           object's current location and image
#      changeFacing: a routine to provide a new facing direction
#           for the object
#      changeSpeed: a routine to increase or decrease the object's
#           current velocity
#      changeScale: a routine to zoom in/out on the object
#           (scaling the size of its image)

class GameObject(pygame.sprite.Sprite):

   # the constructor (initialization routine) for the
   #    movable game objects
   def __init__(self, image, x, y, direction, speed):

      # initialize a pygame sprite for the object
      pygame.sprite.Sprite.__init__(self)

      # load an image for the object
      self.origImage = pygame.image.load(image)
      self.image = self.origImage

      # set up the initial position for the object
      self.position = self.x, self.y = x, y

      # set up the initial direction for the object
      self.facingDir = direction

      # set up the initial scale (zoom) for the object
      self.scale = 1.0      

      # set up the initial speed for the object
      self.velocity = speed

      # initially treat the object as unselected
      self.selected = False


   # the update routine adjusts the object's current position and
   #     image based on its speed and direction
   def update(self):

      # calculate the object's new position based on its old position, 
      #    and its current facing and velocity
      if (self.facingDir == 0):
         self.x = self.x + self.velocity
	 self.y = self.y - self.velocity
      elif (self.facingDir == 90):
         self.x = self.x - self.velocity
	 self.y = self.y - self.velocity
      elif (self.facingDir == 180):
         self.x = self.x - self.velocity
	 self.y = self.y + self.velocity
      elif (self.facingDir == 270):
         self.x = self.x + self.velocity
	 self.y = self.y + self.velocity
      self.position = self.x, self.y

      # update (rotate and zoom) the image for the object
      self.image = pygame.transform.rotozoom(self.origImage, self.facingDir, self.scale)

      # position the image correctly
      self.rect = self.image.get_rect()
      self.rect.center = self.position


   # the turning routine turns the ship 90 degrees clockwise if
   #   the turning direction is right ('r') or 90 degrees
   #   counterclockwise if the turning direction is left ('l')
   def changeFacing(self, dir):

      # if the direction is left then
      #    turn the object 90 degrees counterclockwise
      if (dir == 'l'):
         self.facingDir = self.facingDir + 90
	 if self.facingDir >= 360:
	    self.facingDir = self.facingDir - 360

      # otherwise, if the direction is right then
      #    turn the object 90 degrees counterclockwise
      elif (dir == 'r'):
         self.facingDir = self.facingDir - 90
	 if self.facingDir < 0:
	    self.facingDir = self.facingDir + 360


   # the speed change routine increases the object's velocity
   #     if the change is '+', or decreases the velocity
   #     if the change is '-'
   def changeSpeed(self, change):

      # if the change is '+' then double the current speed
      #    (or increase to 1 if the speed used to be 0)
      if (change == '+'):
         self.velocity = self.velocity * 2
	 if self.velocity == 0:
	    self.velocity = 1

      # otherwise, if the change is '-' then cut the 
      #    current speed in half
      elif (change == '-'):
         self.velocity = self.velocity / 2


   # the zoom routine zooms in on the object (makes it larger)
   #   if the scale is '+' or zooms out (makes it smaller)
   #   if the scale is '-'
   def changeScale(self, scale):

      # zoom in on the object if the scale is '+'
      if (scale == '+'):
         self.scale = self.scale * 1.25 

      # otherwise zoom out if the scale is '-'
      elif (scale == '-'):
         self.scale = self.scale / 1.25 


   # the object selection routine notifies the object
   #     that it has just been selected or deselected
   def changeSelected(self, selFlag):

      # if the passed flag is True then store the fact that
      #    this object is now the selected one
      if selFlag:
         self.selected = True

      # otherwise store the fact that this object is no 
      #    longer the selected one
      else:
         self.selected = False


# =====================================================================
# EVENT HANDLING ROUTINE
#    - processes any pending in-game events,
#      returns which object is currently selected
#      (since this can be changed by some events)

# the routine expects to be given a list of the game objects
#     currently available (objList) and which object is 
#     currently selected/controlled by the player (selObj)

def processEvents(objList, selObj):

   # specify which global variables the routine needs access to
   global keepPlaying

   # process each pending event
   for event in pygame.event.get():

      # if the user closed the window set keepPlaying to False
      #    to tell the game to quit playing
      if event.type == pygame.QUIT:
         keepPlaying = False

      # check if the user has pressed a key
      elif event.type == pygame.KEYDOWN:

         # the escape and q keys quit the game
         if event.key == pygame.K_ESCAPE:
	    keepPlaying = False
         elif event.key == pygame.K_q:
	    keepPlaying = False

         # the left and right arrows turn the currently selected ship
	 #     counterclockwise or clockwise, respectively
         elif event.key == pygame.K_LEFT:
	    print 'turning left with ship ', selObj
	    objList[selObj].changeFacing('l')
         elif event.key == pygame.K_RIGHT:
	    print 'turning right with ship ', selObj
	    objList[selObj].changeFacing('r')

         # the up and down arrows cause the currently selected ship
	 #     to speed up or slow down, respectively
         elif event.key == pygame.K_UP:
	    print 'speeding up ship ', selObj
	    objList[selObj].changeSpeed('+')
         elif event.key == pygame.K_DOWN:
	    print 'slowing down ship ', selObj
	    objList[selObj].changeSpeed('-')

	 # the plus and minus keys zoom in/out on the currently
	 #    selected ship
         elif event.key == pygame.K_EQUALS:
	    print 'zooming in on ship ', selObj
	    objList[selObj].changeScale('+')
         elif event.key == pygame.K_MINUS:
	    print 'zooming out on ship ', selObj
	    objList[selObj].changeScale('-')

	 # the tab key scrolls through the list of ships,
	 #     changing which one is currently selected
         elif event.key == pygame.K_TAB:
	    objList[selObj].changeSelected(False)
	    selObj = selObj + 1
	    if (selObj >= len(objList)):
	       selObj = 0
	    objList[selObj].changeSelected(True)
	    print 'selected ship ', selObj

   # return the currently selected object number
   # (may have been updated by one of the events processed)
   return selObj 


# =====================================================================
# MAIN GAME CONTROL ROUTINE
#    - sets up the game and runs the main game update loop
#      until instructed to quit

def main():

   # identify any global variables the main routine needs to access
   global gameScreen, backImage, scrRefreshRate, keepPlaying

   # initialize pygame and the game's display screen
   gameSetup()

   # create the list of screen update sections (rectangles)
   updateSections = None

   # create an array of objects to add to the display,
   # giving each of them 
   #      an image, xcoord, ycoord, facing, and speed
   gameObjList = [ 
               GameObject('ship.gif', 80, 60, 270, 1),
               GameObject('ship.gif', 560, 60, 180, 2),
               GameObject('ship.gif', 560, 420, 90, 0) 
	       ]

   # indicate which game object is currently 'selected'
   selectedObj = 0
   gameObjList[selectedObj].changeSelected(True)

   # create a group for the game objects
   objGroup = pygame.sprite.RenderUpdates(*gameObjList)

   # run the main game loop
   keepPlaying = True
   while keepPlaying:

      # handle any pending events
      #  (processEvents needs the list of objects that might
      #   be affected, and which object is currently selected)
      selectedObj = processEvents(gameObjList, selectedObj)

      # update the display of the object groups
      objGroup.clear(gameScreen, backImage)

      # run the updates on each object in the group
      objGroup.update()

      # update the display rectangles
      updateSections = objGroup.draw(gameScreen)

      # update the buffered display
      pygame.display.update(updateSections)

      # switch to the new display image
      pygame.display.flip()

      # pause before initiating the next loop cycle
      pygame.time.delay(scrRefreshRate)


# =====================================================================
# INITIATE THE GAME
#    - calls the main() routine
if __name__ == "__main__":
   main()

